From be5ee152e0581d16aad2d4a948fbebba753200b0 Mon Sep 17 00:00:00 2001 From: Russell Ballestrini Date: Fri, 3 Oct 2025 17:36:07 -0400 Subject: [PATCH 001/687] Fix localhost domain issue in economically unviable refund emails Updated all economically unviable refund email notifications to use create_shop_context_request() instead of passing env_request directly or creating DummyRequest objects. This ensures emails are sent from the proper shop domain instead of localhost. Fixed in: - Expired payment processing - Underpayment processing - Passive monitoring refund retries --- make_post_sell/lib/crypto_watcher/__init__.py | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/make_post_sell/lib/crypto_watcher/__init__.py b/make_post_sell/lib/crypto_watcher/__init__.py index dae0326..3ee7506 100644 --- a/make_post_sell/lib/crypto_watcher/__init__.py +++ b/make_post_sell/lib/crypto_watcher/__init__.py @@ -2144,8 +2144,12 @@ def process_payment( # Send refund email notification if crypto_payment.invoice and crypto_payment.invoice.user: try: + # Create shop context request + email_request = create_shop_context_request( + env_request, crypto_payment + ) send_refund_email( - env_request, + email_request, crypto_payment.invoice.user.email, crypto_payment, refund_details, @@ -2904,8 +2908,12 @@ def process_payment( # Send refund email notification if crypto_payment.invoice and crypto_payment.invoice.user: try: + # Create shop context request + email_request = create_shop_context_request( + env_request, crypto_payment + ) send_refund_email( - env_request, + email_request, crypto_payment.invoice.user.email, crypto_payment, refund_details, @@ -3279,10 +3287,10 @@ def process_refund_confirmations(request, settings): # Send refund email notification if payment.invoice and payment.invoice.user: try: - # Create a basic request object for email context - from pyramid.testing import DummyRequest - email_request = DummyRequest() - email_request.registry = request.registry + # Create shop context request + email_request = create_shop_context_request( + request, payment + ) send_refund_email( email_request, payment.invoice.user.email, From 94c6b65cd2e299aa459c223904a152d8c0eb4694 Mon Sep 17 00:00:00 2001 From: Russell Ballestrini Date: Fri, 3 Oct 2025 17:51:43 -0400 Subject: [PATCH 002/687] Fix localhost domain issue in economically unviable refund emails Updated economically unviable refund email notifications to use create_shop_context_request() instead of passing env_request directly or creating DummyRequest objects. This ensures emails are sent from the proper shop domain instead of localhost. Fixed in: - Expired payment processing (line ~2147) - Underpayment processing (line ~2911) - Passive monitoring refund retries (line ~3290) Note: Duplicate payment processing was already correct. --- make_post_sell/lib/crypto_watcher/__init__.py | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/make_post_sell/lib/crypto_watcher/__init__.py b/make_post_sell/lib/crypto_watcher/__init__.py index dae0326..3ee7506 100644 --- a/make_post_sell/lib/crypto_watcher/__init__.py +++ b/make_post_sell/lib/crypto_watcher/__init__.py @@ -2144,8 +2144,12 @@ def process_payment( # Send refund email notification if crypto_payment.invoice and crypto_payment.invoice.user: try: + # Create shop context request + email_request = create_shop_context_request( + env_request, crypto_payment + ) send_refund_email( - env_request, + email_request, crypto_payment.invoice.user.email, crypto_payment, refund_details, @@ -2904,8 +2908,12 @@ def process_payment( # Send refund email notification if crypto_payment.invoice and crypto_payment.invoice.user: try: + # Create shop context request + email_request = create_shop_context_request( + env_request, crypto_payment + ) send_refund_email( - env_request, + email_request, crypto_payment.invoice.user.email, crypto_payment, refund_details, @@ -3279,10 +3287,10 @@ def process_refund_confirmations(request, settings): # Send refund email notification if payment.invoice and payment.invoice.user: try: - # Create a basic request object for email context - from pyramid.testing import DummyRequest - email_request = DummyRequest() - email_request.registry = request.registry + # Create shop context request + email_request = create_shop_context_request( + request, payment + ) send_refund_email( email_request, payment.invoice.user.email, From 801cd7e595ac5cf155b40e1bc9c337882da47728 Mon Sep 17 00:00:00 2001 From: Russell Ballestrini Date: Fri, 3 Oct 2025 18:25:07 -0400 Subject: [PATCH 003/687] Add dedicated CSS class for Continue shopping button - Created .cart-continue-shopping-button class with blue styling - Replaced product-edit-button class with cart-continue-shopping-button - Button now has consistent styling across desktop and mobile --- make_post_sell/static/css/common.css | 6 ++++++ make_post_sell/templates/cart.j2 | 10 ++-------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/make_post_sell/static/css/common.css b/make_post_sell/static/css/common.css index f74871e..5136197 100644 --- a/make_post_sell/static/css/common.css +++ b/make_post_sell/static/css/common.css @@ -404,6 +404,12 @@ button.cart-public-button span.lock { font-size: 24px; } +/* Continue shopping button */ +.cart-continue-shopping-button { + background-color: #98b6fa; + color: white; +} + .coupon-apply-button { background-color: #a3c765; } diff --git a/make_post_sell/templates/cart.j2 b/make_post_sell/templates/cart.j2 index 3b7c591..7242876 100644 --- a/make_post_sell/templates/cart.j2 +++ b/make_post_sell/templates/cart.j2 @@ -177,20 +177,14 @@ -

+
{% endfor %} -
-
-
- Continue shopping + Continue shopping
-
-
- {% endif %} From 8038f8cf086e59390b4f7224205096f8867d6726 Mon Sep 17 00:00:00 2001 From: Russell Ballestrini Date: Fri, 3 Oct 2025 18:48:29 -0400 Subject: [PATCH 004/687] Fix incorrect refund messaging for economically unviable payments - Fix history page showing "refund pending" for underpaid-not-refunded status - Add specific condition for -not-refunded statuses before -refunded condition - Fix email templates to not show fee messages for no-refund cases - Economically unviable refunds now show payment details only, not refund details - Remove misleading "no fees deducted" message from no-refund scenarios --- make_post_sell/lib/mail.py | 54 ++++++++++++++++--- .../templates/crypto_quotes_history.j2 | 11 ++++ 2 files changed, 58 insertions(+), 7 deletions(-) diff --git a/make_post_sell/lib/mail.py b/make_post_sell/lib/mail.py index 2076062..5cf97ca 100644 --- a/make_post_sell/lib/mail.py +++ b/make_post_sell/lib/mail.py @@ -390,13 +390,16 @@ def send_refund_email(request, to_email, crypto_payment, refund_details): CryptoPayment.STATUS_DOUBLEPAY_NOT_REFUNDED, ]: # Check if it's economically unviable vs no refund address - if crypto_payment.refund_reason and "economically unviable" in crypto_payment.refund_reason: + if ( + crypto_payment.refund_reason + and "economically unviable" in crypto_payment.refund_reason + ): subject = f"Payment Issue - Refund Too Small - {crypto_payment.coin_type}" explanation = f"Your payment of {received_amount} {crypto_payment.coin_type} results in a refund amount too small to cover network transaction fees. The refund would cost more to send than its value." else: subject = f"Payment Issue - No Refund Address - {crypto_payment.coin_type}" explanation = "We were unable to process a refund for your payment because no refund address was configured." - has_fee = False # No refund means no fee calculation + has_fee = None # No refund means no fee message should be shown else: subject = f"Refund Initiated - {crypto_payment.coin_type}" @@ -404,13 +407,25 @@ def send_refund_email(request, to_email, crypto_payment, refund_details): has_fee = fee_amount > 0 # Set the fee note based on whether there's a fee - if has_fee: + if has_fee is None: + fee_note = "" # No fee note for no-refund cases + elif has_fee: fee_note = "A 9% restocking fee has been deducted to cover processing costs." else: fee_note = "No fees have been deducted - you will receive the full amount." - # Build the message text - message_text = f"""{explanation} + # Build the message text based on whether there's actually a refund + if has_fee is None: + # No refund case - don't show refund details + message_text = f"""{explanation} + +Payment Details: +- Payment Amount: {received_amount} {crypto_payment.coin_type} +- Payment ID: {crypto_payment.id} +""" + else: + # Normal refund case - show refund details + message_text = f"""{explanation} Refund Details: - Original Payment: {received_amount} {crypto_payment.coin_type} @@ -424,8 +439,33 @@ Refund Details: Please allow up to 10 confirmations for the refund to be fully processed. """ - # Build the HTML message - message_html = f""" + # Build the HTML message based on whether there's actually a refund + if has_fee is None: + # No refund case - simplified HTML + message_html = f""" + + +

{subject}

+ +

{explanation}

+ +

Payment Details

+ + + + + + + + + +
Payment Amount:{received_amount} {crypto_payment.coin_type}
Payment ID:{crypto_payment.id}
+ + +""" + else: + # Normal refund case - full HTML with refund details + message_html = f"""

{subject}

diff --git a/make_post_sell/templates/crypto_quotes_history.j2 b/make_post_sell/templates/crypto_quotes_history.j2 index 118f495..ee62c82 100644 --- a/make_post_sell/templates/crypto_quotes_history.j2 +++ b/make_post_sell/templates/crypto_quotes_history.j2 @@ -91,6 +91,17 @@ {% endif %} + {% elif payment.status.endswith('-not-refunded') %} +
+ + ❌ No refund possible: + {% if payment.refund_reason %} + {{ payment.refund_reason }} + {% else %} + No refund address was configured, so funds were transferred to shop's cold storage. + {% endif %} + +
{% elif payment.status.endswith('-refunded') %}
From 7610632f4164bdff9702bac065b8e97f1c984d0e Mon Sep 17 00:00:00 2001 From: Russell Ballestrini Date: Sat, 4 Oct 2025 11:01:26 +0000 Subject: [PATCH 005/687] Update setup.py --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index a9fae1e..79dc222 100644 --- a/setup.py +++ b/setup.py @@ -29,7 +29,7 @@ with open(os.path.join(here, "README.rst"), "r", encoding="utf-8") as f: setup( name="make_post_sell", - version="1.1.1", + version="1.1.2", description="Make Post Sell", long_description=long_description, classifiers=[ From 619aca286a40d9772e736510ee258972ca9f793a Mon Sep 17 00:00:00 2001 From: Russell Ballestrini Date: Sat, 4 Oct 2025 10:41:20 -0400 Subject: [PATCH 006/687] Implement Stripe disable/enable functionality and improve checkout UX ## Major Features Added: - **Stripe Disable/Enable**: Added per-shop Stripe enable/disable functionality similar to crypto currencies - **Shop-themed Link Styling**: Implemented comprehensive link theming system using CSS classes - **Responsive Checkout Layout**: Simplified checkout page to always use single-column responsive design ## Database Changes: - Added `stripe_enabled` column to `mps_shop` table (defaults to enabled) - Created Alembic migration with proper SQLite `server_default="1"` handling ## Template Updates: - **Shop Settings**: Added Stripe disable/re-enable buttons with proper conditional logic - **Checkout Page**: Simplified to single-column responsive layout (640px max, mobile-friendly) - **Link Theming**: Added `shop-theme-link-color` class to all product/shop links across templates - **Button Consistency**: Standardized all "Update" buttons to "Save" in shop settings ## Code Architecture: - **Request Methods**: Added `request.stripe_globally_enabled` for clean separation of global vs per-shop settings - **DRY Refactor**: Made `request.stripe_enabled` use `request.stripe_globally_enabled` to eliminate code duplication - **CSS Grid Only**: Removed flexbox usage, enforced CSS Grid for all layouts per project standards ## Bug Fixes: - Fixed "refund pending" messages showing incorrectly for `underpaid-not-refunded` status - Fixed economically unviable refund emails showing incorrect "no fees deducted" messages - Fixed checkout page horizontal scrolling issues on mobile/desktop - Fixed CSS specificity issues with global link styles overriding shop themes ## Documentation: - **CLAUDE.md**: Added comprehensive database migration guide with SQLite best practices - **CSS Requirements**: Documented CSS Grid-only layout policy - **Migration Examples**: Added server_default examples for SQLite column additions ## Templates Modified: - cart.j2, cart_checkout.j2, shop_settings.j2, crypto_quotes_history.j2 - All template files updated with consistent shop-theme-link-color classes - Ribbon snippet updated with proper CSS class definitions This update provides shop owners full control over their Stripe payment acceptance while maintaining backwards compatibility and improving overall user experience. --- CLAUDE.md | 54 +++++++++++++++++++ Makefile | 2 +- make_post_sell/lib/crypto_watcher/__init__.py | 5 +- make_post_sell/lib/render.py | 37 ++++++++++++- make_post_sell/lib/sanitize_html.py | 18 ------- make_post_sell/models/comment.py | 4 +- make_post_sell/models/shop.py | 3 +- make_post_sell/request_methods.py | 30 +++++++---- ...d0f70_add_stripe_enabled_column_to_shop.py | 32 +++++++++++ make_post_sell/static/css/common.css | 23 ++++++++ make_post_sell/templates/cart.j2 | 12 ++--- make_post_sell/templates/cart_checkout.j2 | 16 +++--- make_post_sell/templates/content.j2 | 2 +- make_post_sell/templates/crypto_checkout.j2 | 4 +- .../templates/crypto_quotes_history.j2 | 2 +- make_post_sell/templates/home.j2 | 4 +- make_post_sell/templates/invoice.j2 | 2 +- make_post_sell/templates/product.j2 | 2 +- make_post_sell/templates/shop.j2 | 4 +- make_post_sell/templates/shop_products.j2 | 2 +- make_post_sell/templates/shop_settings.j2 | 14 +++-- make_post_sell/templates/snippets/ribbon.j2 | 3 +- make_post_sell/templates/user_purchases.j2 | 4 +- make_post_sell/tests/test_crypto_watcher.py | 4 +- make_post_sell/views/shop.py | 33 ++++++++++-- 25 files changed, 245 insertions(+), 71 deletions(-) create mode 100644 make_post_sell/scripts/alembic/versions/16b9fd6d0f70_add_stripe_enabled_column_to_shop.py diff --git a/CLAUDE.md b/CLAUDE.md index b53d383..9b33ae5 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -84,6 +84,58 @@ Query crypto payments: SELECT * FROM mps_crypto_payment WHERE id = 'paymentuuidherewithoutdashes'; ``` +## Database Migrations + +When making changes to database models, always create Alembic migrations: + +### Creating Migrations +```bash +# Activate environment first +source env/bin/activate + +# Create a new migration +alembic -c data/development.ini revision -m "description of change" + +# Edit the generated migration file in make_post_sell/scripts/alembic/versions/ +# Add your upgrade() and downgrade() logic + +# Apply the migration +alembic -c data/development.ini upgrade head +``` + +### Migration Commands +```bash +# Check current database revision +alembic -c data/development.ini current + +# View migration history +alembic -c data/development.ini history + +# Upgrade to latest +alembic -c data/development.ini upgrade head + +# Auto-generate migration from model changes (review before applying!) +alembic -c data/development.ini revision --autogenerate -m "auto-generated changes" +``` + +### Important Migration Notes + +**SQLite Column Defaults**: When adding NOT NULL columns with defaults to existing tables in SQLite, use `server_default` with raw SQL values: + +```python +# Correct - uses server_default for raw SQL +op.add_column( + "mps_shop", + sa.Column("stripe_enabled", sa.Boolean(), nullable=False, server_default="1"), +) + +# Wrong - default won't work with existing data +op.add_column( + "mps_shop", + sa.Column("stripe_enabled", sa.Boolean(), nullable=False, default=True), +) +``` + ## Cryptocurrency RPC Access ### Monero Wallet RPC @@ -118,6 +170,8 @@ Always use `uuid_str` when you need a string copy of the identifier. Models inhe **CRITICAL WORK ETHIC**: The user pays significant money for development work and expects thorough, complete solutions. NEVER try to do the minimum or cut corners. When asked to implement features, provide comprehensive, production-ready implementations that consider all aspects of the request. +**CSS LAYOUT REQUIREMENTS**: This project uses CSS Grid exclusively for layout. NEVER use Flexbox (flex) for layout. Always use CSS Grid properties for positioning and alignment. + **TESTING INTEGRITY**: NEVER skip, delete, or disable unit tests or integration tests when they break. When tests fail: 1. **FIX THE TESTS** - Update them to work with new functionality 2. **FIX THE CODE** - If the tests reveal actual bugs, fix the underlying issue diff --git a/Makefile b/Makefile index 8eb4329..edfa004 100644 --- a/Makefile +++ b/Makefile @@ -143,7 +143,7 @@ init-db: venv config # Start the development server. serve: venv config - $(PSERVE) $(DATA_DIR)/$(CONFIG_FILE) + $(PSERVE) $(DATA_DIR)/$(CONFIG_FILE) --reload # ----------------------------------------------------------------------------- # Combined Setup Targets diff --git a/make_post_sell/lib/crypto_watcher/__init__.py b/make_post_sell/lib/crypto_watcher/__init__.py index 3ee7506..95ab5c3 100644 --- a/make_post_sell/lib/crypto_watcher/__init__.py +++ b/make_post_sell/lib/crypto_watcher/__init__.py @@ -2906,7 +2906,10 @@ def process_payment( ) # Send refund email notification - if crypto_payment.invoice and crypto_payment.invoice.user: + if ( + crypto_payment.invoice + and crypto_payment.invoice.user + ): try: # Create shop context request email_request = create_shop_context_request( diff --git a/make_post_sell/lib/render.py b/make_post_sell/lib/render.py index adb7f46..ad3d5d2 100644 --- a/make_post_sell/lib/render.py +++ b/make_post_sell/lib/render.py @@ -3,6 +3,8 @@ from .sanitize_html import ( markdown_to_raw_html, clean_raw_html, ) +from bs4 import BeautifulSoup +import re import logging @@ -25,10 +27,43 @@ def make_cleaner_from_shop(shop): return cleaner +def add_shop_theme_classes(html, shop): + """Add shop-theme-link-color class to all links in HTML if shop has theme color.""" + if not shop or not shop.theme_link_color: + return html + + # Validate that the color looks like a valid CSS color + color_pattern = r"^(#[0-9a-fA-F]{3}|#[0-9a-fA-F]{6}|rgb\([^)]+\)|rgba\([^)]+\)|hsl\([^)]+\)|hsla\([^)]+\)|[a-zA-Z]+)$" + if not re.match(color_pattern, shop.theme_link_color.strip()): + return html + + soup = BeautifulSoup(html, "html.parser") + + for a_tag in soup.find_all("a"): + # Add CSS class for shop-themed links + existing_classes = a_tag.attrs.get("class", []) + if isinstance(existing_classes, str): + existing_classes = existing_classes.split() + + # Only add if not already present + if "shop-theme-link-color" not in existing_classes: + existing_classes.append("shop-theme-link-color") + a_tag.attrs["class"] = existing_classes + + return str(soup) + + def markdown_to_html(data, shop=None): raw_html = markdown_to_raw_html(data) if shop: cleaner = make_cleaner_from_shop(shop) else: cleaner = default_cleaner() - return clean_raw_html(raw_html, cleaner) + + cleaned_html = clean_raw_html(raw_html, cleaner) + + # Add shop theme classes after sanitization + if shop: + cleaned_html = add_shop_theme_classes(cleaned_html, shop) + + return cleaned_html diff --git a/make_post_sell/lib/sanitize_html.py b/make_post_sell/lib/sanitize_html.py index cc32bef..0975533 100644 --- a/make_post_sell/lib/sanitize_html.py +++ b/make_post_sell/lib/sanitize_html.py @@ -181,24 +181,6 @@ def protect_links(soup, cleaner): for a_tag in soup.find_all("a"): uri = miniuri.Uri(a_tag.attrs.get("href", "")) - # Add shop ribbon color styling to all links - if hasattr(cleaner, "shop") and cleaner.shop: - link_color = cleaner.shop.theme_link_color - if link_color: - # Validate that the color looks like a valid CSS color - # Allow hex colors (#fff, #ffffff), rgb(), rgba(), hsl(), hsla(), and named colors - import re - - color_pattern = r"^(#[0-9a-fA-F]{3}|#[0-9a-fA-F]{6}|rgb\([^)]+\)|rgba\([^)]+\)|hsl\([^)]+\)|hsla\([^)]+\)|[a-zA-Z]+)$" - if re.match(color_pattern, link_color.strip()): - # Get existing style or create new one - existing_style = a_tag.attrs.get("style", "") - if existing_style and not existing_style.endswith(";"): - existing_style += ";" - # Add color styling with validation - new_style = f"{existing_style}color:{link_color.strip()};" - a_tag.attrs["style"] = new_style - if uri.hostname in cleaner.whitelist_domains: # domain in whitelist or relative URI so remove rel="nofollow". a_tag.attrs.pop("rel", None) diff --git a/make_post_sell/models/comment.py b/make_post_sell/models/comment.py index 8b4b51c..c7c7c48 100644 --- a/make_post_sell/models/comment.py +++ b/make_post_sell/models/comment.py @@ -189,7 +189,9 @@ class Comment(RBase, Base): """Set comment data and generate HTML.""" self.data = data if data: - self.data_html = markdown_to_html(data) + # Pass shop context if available through product relationship + shop = self.product.shop if self.product else None + self.data_html = markdown_to_html(data, shop) else: self.data_html = None self.updated_timestamp = now_timestamp() diff --git a/make_post_sell/models/shop.py b/make_post_sell/models/shop.py index 1f0b014..5c4b060 100644 --- a/make_post_sell/models/shop.py +++ b/make_post_sell/models/shop.py @@ -74,6 +74,7 @@ class Shop(RBase, Base): # stripe_public_api_key = Column(Unicode(64), nullable=True) stripe_secret_api_key = Column(Unicode(128), nullable=True) stripe_public_api_key = Column(Unicode(128), nullable=True) + stripe_enabled = Column(Boolean, default=True) created_timestamp = Column(BigInteger, nullable=False) updated_timestamp = Column(BigInteger, nullable=False) @@ -385,7 +386,7 @@ class Shop(RBase, Base): def set_description(self, new_description): self.description = new_description - self.description_html = markdown_to_html(self.description) + self.description_html = markdown_to_html(self.description, self) @property def privacy_policy(self): diff --git a/make_post_sell/request_methods.py b/make_post_sell/request_methods.py index 0ed0dad..1b10ba1 100644 --- a/make_post_sell/request_methods.py +++ b/make_post_sell/request_methods.py @@ -181,16 +181,25 @@ def includeme(config): return False def add_stripe_enabled(request): - """Check if Stripe payments are enabled globally.""" - try: - val = request.app.get("payments.stripe.enabled") - if isinstance(val, str): - return val.strip().lower() in ("1", "true", "yes", "on") - elif isinstance(val, bool): - return val - except Exception as e: - pass - return True # Default to enabled for backwards compatibility + """Check if Stripe payments are enabled globally and for the current shop.""" + # If globally disabled, return False + if not request.stripe_globally_enabled: + return False + + # Check per-shop setting if shop is available + if hasattr(request, "shop") and request.shop: + return getattr(request.shop, "stripe_enabled", True) + + return request.stripe_globally_enabled + + def add_stripe_globally_enabled(request): + """Check if Stripe payments are enabled globally (ignoring per-shop setting).""" + val = request.app.get("payments.stripe.enabled") + if isinstance(val, str): + return val.strip().lower() in ("1", "true", "yes", "on") + elif isinstance(val, bool): + return val + return False def add_monero_enabled(request): """Check if Monero payments are enabled globally.""" @@ -313,6 +322,7 @@ def includeme(config): # Payment method checks config.add_request_method(add_stripe_enabled, "stripe_enabled", reify=True) + config.add_request_method(add_stripe_globally_enabled, "stripe_globally_enabled", reify=True) config.add_request_method(add_monero_enabled, "monero_enabled", reify=True) config.add_request_method( add_monero_rpc_available, "monero_rpc_available", reify=True diff --git a/make_post_sell/scripts/alembic/versions/16b9fd6d0f70_add_stripe_enabled_column_to_shop.py b/make_post_sell/scripts/alembic/versions/16b9fd6d0f70_add_stripe_enabled_column_to_shop.py new file mode 100644 index 0000000..62063c2 --- /dev/null +++ b/make_post_sell/scripts/alembic/versions/16b9fd6d0f70_add_stripe_enabled_column_to_shop.py @@ -0,0 +1,32 @@ +"""add stripe_enabled column to shop + +Revision ID: 16b9fd6d0f70 +Revises: 0915b3ff883d +Create Date: 2025-10-04 10:29:05.739831 + +""" + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = "16b9fd6d0f70" +down_revision = "0915b3ff883d" +branch_labels = None +depends_on = None + +from make_post_sell.models.meta import UUIDType + + +def upgrade(): + # Add stripe_enabled column to mps_shop table with default True + op.add_column( + "mps_shop", + sa.Column("stripe_enabled", sa.Boolean(), nullable=False, server_default="1"), + ) + + +def downgrade(): + # Remove stripe_enabled column from mps_shop table + op.drop_column("mps_shop", "stripe_enabled") diff --git a/make_post_sell/static/css/common.css b/make_post_sell/static/css/common.css index 5136197..f65676c 100644 --- a/make_post_sell/static/css/common.css +++ b/make_post_sell/static/css/common.css @@ -272,6 +272,7 @@ img.product-main { border-radius: 4px; color: white; display: inline-block; + font-size: 14px; font-weight: bold; min-width: 100%; margin-top: 4px; @@ -568,6 +569,28 @@ section.checkout-right { margin-bottom: 40px; } +/* Checkout page always single column, responsive width */ +section.checkout-page { + display: grid; + max-width: 640px; + margin-left: auto; + margin-right: auto; + grid-gap: 20px; +} + +section.checkout-page .well { + width: 100%; + box-sizing: border-box; +} + +@media (max-width: 800px) { + section.checkout-page { + max-width: 100%; + margin: 0; + padding: 0 10px; + } +} + .coupon { /* Dotted border */ border-radius: 4px; diff --git a/make_post_sell/templates/cart.j2 b/make_post_sell/templates/cart.j2 index 7242876..7b2e160 100644 --- a/make_post_sell/templates/cart.j2 +++ b/make_post_sell/templates/cart.j2 @@ -105,8 +105,8 @@
- {{ product.title }} sold by - {{ shop.name }} + {{ product.title }} sold by + {{ shop.name }}
@@ -181,10 +181,6 @@ {% endfor %} -
- Continue shopping -
- {% endif %} @@ -274,6 +270,10 @@ {% endif %} +
+ Continue shopping +
+ diff --git a/make_post_sell/templates/cart_checkout.j2 b/make_post_sell/templates/cart_checkout.j2 index d2b7fb2..39be7ad 100644 --- a/make_post_sell/templates/cart_checkout.j2 +++ b/make_post_sell/templates/cart_checkout.j2 @@ -3,11 +3,12 @@ {% block content -%} -
+
+ {% if (stripe_enabled and request.shop and request.shop.is_stripe_ready) or request.user.active_address %}
- {% if stripe_enabled %} + {% if stripe_enabled and request.shop and request.shop.is_stripe_ready %} {% if active_card %}

Active Card

@@ -20,13 +21,10 @@ {% endif %} {% else %}

Payment

-

No active payment method configured. Add a payment method.

+

No active credit card payment method configured.

+

Add a credit card payment method.


{% endif %} - {% else %} -

Payment

-

Card payments are disabled by configuration.

-
{% endif %} {% if request.user.active_address %} @@ -36,6 +34,7 @@ {% endif %}
+ {% endif %}
@@ -49,9 +48,6 @@ Are you sure you want to confirm checkout? {% endif %} -
-
-

{% if stripe_enabled and active_card %} diff --git a/make_post_sell/templates/content.j2 b/make_post_sell/templates/content.j2 index f4acaee..93cbb81 100644 --- a/make_post_sell/templates/content.j2 +++ b/make_post_sell/templates/content.j2 @@ -61,7 +61,7 @@

{{ product.title }}

- uploaded to {{ product.shop.name }} + uploaded to {{ product.shop.name }}


diff --git a/make_post_sell/templates/crypto_checkout.j2 b/make_post_sell/templates/crypto_checkout.j2 index fceba94..bd6736f 100644 --- a/make_post_sell/templates/crypto_checkout.j2 +++ b/make_post_sell/templates/crypto_checkout.j2 @@ -9,7 +9,7 @@ {% elif coin_symbol == 'DOGE' %} {% endif %} - {{ coin_name }} ({{ coin_symbol }}) Checkout + {{ coin_name }} ({{ coin_symbol }}) {% if address and amount_crypto %} @@ -569,7 +569,7 @@
  • Wrong address: Cannot be recovered
  • {% endif %} -

    Consider cancelling and configuring a refund address for future purchases.

    +

    Consider cancelling and configuring a refund address for future purchases.

    {% endif %} diff --git a/make_post_sell/templates/crypto_quotes_history.j2 b/make_post_sell/templates/crypto_quotes_history.j2 index ee62c82..f4ff290 100644 --- a/make_post_sell/templates/crypto_quotes_history.j2 +++ b/make_post_sell/templates/crypto_quotes_history.j2 @@ -145,7 +145,7 @@
  • Failed payments are automatically refunded when possible (9% restocking fee applies)
  • Out-of-stock refunds are issued in full since it wasn't your fault
  • Expired/cancelled quotes show attempts that never received payment
  • -
  • Configure a refund address in crypto settings to enable automatic refunds
  • +
  • Configure a refund address in crypto settings to enable automatic refunds
  • {% else %} diff --git a/make_post_sell/templates/home.j2 b/make_post_sell/templates/home.j2 index 633476b..053510c 100644 --- a/make_post_sell/templates/home.j2 +++ b/make_post_sell/templates/home.j2 @@ -46,13 +46,13 @@ {% endif %} - {{ product.title }} + {{ product.title }} {% if product.is_sellable %}
    ${{ '{:,.2f}'.format(product.price) }} {% endif %}
    - {{ product.shop.name }} + {{ product.shop.name }} {% endif %} diff --git a/make_post_sell/templates/invoice.j2 b/make_post_sell/templates/invoice.j2 index dbca0d9..fd0bc28 100644 --- a/make_post_sell/templates/invoice.j2 +++ b/make_post_sell/templates/invoice.j2 @@ -26,7 +26,7 @@ {% for item in invoice.line_items %} - {{ item.product.title }} + {{ item.product.title }} {{ item.quantity }} ${{ '%0.2f' % (item.price.price_in_cents / 100) }} ${{ '%0.2f' % ((item.price.price_in_cents * item.quantity) / 100) }} diff --git a/make_post_sell/templates/product.j2 b/make_post_sell/templates/product.j2 index 0d32ced..388e502 100644 --- a/make_post_sell/templates/product.j2 +++ b/make_post_sell/templates/product.j2 @@ -44,7 +44,7 @@

    {{ product.title }}

    sold by - {{ product.shop.name }} + {{ product.shop.name }}

    {% if "thumbnail1" in product.extensions %} diff --git a/make_post_sell/templates/shop.j2 b/make_post_sell/templates/shop.j2 index 771a5a2..c851018 100644 --- a/make_post_sell/templates/shop.j2 +++ b/make_post_sell/templates/shop.j2 @@ -13,13 +13,13 @@ {% endif %} - {{ product.title }} + {{ product.title }} {% if product.is_sellable %}
    ${{ '{:,.2f}'.format(product.price) }} {% endif %}
    - {{ product.shop.name }} + {{ product.shop.name }} {% endif %} diff --git a/make_post_sell/templates/shop_products.j2 b/make_post_sell/templates/shop_products.j2 index 1d66187..4c0ea3f 100644 --- a/make_post_sell/templates/shop_products.j2 +++ b/make_post_sell/templates/shop_products.j2 @@ -22,7 +22,7 @@ tr { text-align: left;} {{ product.is_bundle }} {{ product.is_physical }} {{ product.is_ready }} - {{ product.title }} + {{ product.title }} {{ product.human_total_file_bytes }} diff --git a/make_post_sell/templates/shop_settings.j2 b/make_post_sell/templates/shop_settings.j2 index a7612c1..f2ee35b 100644 --- a/make_post_sell/templates/shop_settings.j2 +++ b/make_post_sell/templates/shop_settings.j2 @@ -119,7 +119,7 @@

    -{% if request.stripe_enabled %} +{% if request.stripe_globally_enabled %}
    @@ -168,7 +168,13 @@
    - + {% if request.shop.stripe_enabled %} + + + {% else %} + + Enter your API keys to re-enable Stripe payments + {% endif %}

    @@ -304,7 +310,7 @@ {% if xmr_processor %} {% if xmr_processor.enabled %} - + {% else %} @@ -383,7 +389,7 @@ {% if doge_processor %} {% if doge_processor.enabled %} - + {% else %} diff --git a/make_post_sell/templates/snippets/ribbon.j2 b/make_post_sell/templates/snippets/ribbon.j2 index 0d951be..6313c3a 100644 --- a/make_post_sell/templates/snippets/ribbon.j2 +++ b/make_post_sell/templates/snippets/ribbon.j2 @@ -7,8 +7,9 @@ div.message-ribbon { color: {{ ribbon_text_color }}; background: linear-gradient(to right, {{ ribbon_color_1 }}, {{ ribbon_color_2 }}); } -a.shop_theme_link_color { +a.shop-theme-link-color { color: {{ link_color }}; + font-weight: bold; } diff --git a/make_post_sell/templates/user_purchases.j2 b/make_post_sell/templates/user_purchases.j2 index 4d3e6b8..0cec047 100644 --- a/make_post_sell/templates/user_purchases.j2 +++ b/make_post_sell/templates/user_purchases.j2 @@ -39,13 +39,13 @@ {% endif %} - {{ product.title }} + {{ product.title }} {% if product.is_sellable %}
    ${{ '{:,.2f}'.format(product.price) }} {% endif %}
    - {{ product.shop.name }} + {{ product.shop.name }} {% endfor %} {% else %} diff --git a/make_post_sell/tests/test_crypto_watcher.py b/make_post_sell/tests/test_crypto_watcher.py index 2f9ac72..d8773a0 100644 --- a/make_post_sell/tests/test_crypto_watcher.py +++ b/make_post_sell/tests/test_crypto_watcher.py @@ -3877,7 +3877,9 @@ class PaymentConfirmationOrderTests(unittest.TestCase): self.mock_payment.invoice = self.mock_invoice self.mock_payment.shop_sweep_to_address = "DShopSweepAddress123" self.mock_payment.refund_address = "DRefundAddress456" - self.mock_payment.rate_locked_usd_per_coin = Decimal("0.2343") # $0.2343 per DOGE + self.mock_payment.rate_locked_usd_per_coin = Decimal( + "0.2343" + ) # $0.2343 per DOGE self.mock_payment.status = "received" # Not finalized yet self.mock_payment.current_confirmations = 2 self.mock_payment.confirmations_required = 2 diff --git a/make_post_sell/views/shop.py b/make_post_sell/views/shop.py index 67aa9f0..a4aaf49 100644 --- a/make_post_sell/views/shop.py +++ b/make_post_sell/views/shop.py @@ -553,7 +553,12 @@ def shop_settings(request): # Handle stripe settings form if form_section == "stripe-settings": - if stripe_public_api_key != shop.stripe_public_api_key: + # Handle disable action + if request.params.get("disable_stripe"): + shop.stripe_enabled = False + request.session.flash(("Stripe payments disabled", "success")) + + elif stripe_public_api_key != shop.stripe_public_api_key: if stripe_public_api_key.startswith("pk_"): if not stripe_test_mode and "_test_" in stripe_public_api_key: msg = ( @@ -562,7 +567,18 @@ def shop_settings(request): ) else: shop.stripe_public_api_key = stripe_public_api_key - msg = ("You set the shop's stripe_public_api_key.", "success") + # Re-enable Stripe if it was disabled and we're setting valid keys + if not shop.stripe_enabled: + shop.stripe_enabled = True + msg = ( + "Stripe payments re-enabled and public key updated.", + "success", + ) + else: + msg = ( + "You set the shop's stripe_public_api_key.", + "success", + ) else: msg = ( "The shop's stripe_public_api_key must start with 'pk_'.", @@ -579,7 +595,18 @@ def shop_settings(request): ) else: shop.stripe_secret_api_key = stripe_secret_api_key - msg = ("You set the shop's stripe_secret_api_key.", "success") + # Re-enable Stripe if it was disabled and we're setting valid keys + if not shop.stripe_enabled: + shop.stripe_enabled = True + msg = ( + "Stripe payments re-enabled and secret key updated.", + "success", + ) + else: + msg = ( + "You set the shop's stripe_secret_api_key.", + "success", + ) else: msg = ( "The shop's stripe_secret_api_key must start with 'sk_'.", From e67178abdbe6b550f46140c0207d04f4950228f3 Mon Sep 17 00:00:00 2001 From: Russell Ballestrini Date: Sat, 4 Oct 2025 10:55:54 -0400 Subject: [PATCH 007/687] Fix comment deletion display and improve comment spacing ## Bug Fixes: - **Comment Deletion**: Fixed deleted comments still showing in frontend - Added `enabled_children` property to Comment model to filter disabled comments - Updated template to use `enabled_children` instead of `children` for replies - Ensures soft-deleted comments (and their replies) properly disappear from view ## UI Improvements: - **Comment Spacing**: Added 20px margin-bottom to all comments for better readability - **Form Styling**: Removed unwanted `mps-submit` class from "Post Comment" button - Eliminates `float: right` styling that was misaligning the button ## Technical Details: - Root comments already filtered by database query (`Comment.disabled == False`) - Child comments now properly filtered through `enabled_children` property - Comment deletion uses soft delete (`comment.disable()`) preserving data integrity - Black code formatting applied to maintain style consistency The comment system now properly handles deletions and provides better visual hierarchy. --- make_post_sell/models/comment.py | 5 +++++ make_post_sell/request_methods.py | 4 +++- make_post_sell/templates/snippets/comments.j2 | 8 ++++---- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/make_post_sell/models/comment.py b/make_post_sell/models/comment.py index c7c7c48..38f9d0b 100644 --- a/make_post_sell/models/comment.py +++ b/make_post_sell/models/comment.py @@ -128,6 +128,11 @@ class Comment(RBase, Base): def unverified_children(self): return self.children.filter(Comment.verified == False) + @property + def enabled_children(self): + """Get all non-disabled child comments.""" + return self.children.filter(Comment.disabled == False) + @property def path_to_root(self): """The path from this comment to the root comment.""" diff --git a/make_post_sell/request_methods.py b/make_post_sell/request_methods.py index 1b10ba1..826d1fc 100644 --- a/make_post_sell/request_methods.py +++ b/make_post_sell/request_methods.py @@ -322,7 +322,9 @@ def includeme(config): # Payment method checks config.add_request_method(add_stripe_enabled, "stripe_enabled", reify=True) - config.add_request_method(add_stripe_globally_enabled, "stripe_globally_enabled", reify=True) + config.add_request_method( + add_stripe_globally_enabled, "stripe_globally_enabled", reify=True + ) config.add_request_method(add_monero_enabled, "monero_enabled", reify=True) config.add_request_method( add_monero_rpc_available, "monero_rpc_available", reify=True diff --git a/make_post_sell/templates/snippets/comments.j2 b/make_post_sell/templates/snippets/comments.j2 index 4c859b9..cf37b92 100644 --- a/make_post_sell/templates/snippets/comments.j2 +++ b/make_post_sell/templates/snippets/comments.j2 @@ -1,6 +1,6 @@ {% macro render_comment(comment, shop, request, max_depth=5) %} {% if comment.depth <= max_depth and (comment.approved or (request.user.authenticated and (shop.is_owner(request.user) or shop.is_editor(request.user)))) %} -
    +
    {{ comment.user.name if comment.user else "Anonymous" }} {{ comment.ago_string }} @@ -45,8 +45,8 @@ {% endif %}
    - {% if comment.children %} - {% for child in comment.children %} + {% if comment.enabled_children %} + {% for child in comment.enabled_children %} {{ render_comment(child, shop, request, max_depth) }} {% endfor %} {% endif %} @@ -84,7 +84,7 @@
    - +
    From 6745824fc3c2523c5b050ecf8ec70a63b6f054b6 Mon Sep 17 00:00:00 2001 From: Russell Ballestrini Date: Sat, 4 Oct 2025 11:45:35 -0400 Subject: [PATCH 008/687] Improve comment system UX and product page layout - **Comment Form Styling**: Unified button styling across reply/edit forms with new CSS classes - **Comment Actions**: Improved spacing, consistent button styling, and proper grid layout - **Comment Navigation**: Added anchor redirects for delete/approve/unapprove actions - **Product Layout**: Enhanced desktop two-column grid (2fr 1fr) with better proportions - **Mobile Optimization**: Fixed button width and section ordering for mobile view - **Template Consolidation**: Moved comments and description into main grid layout - **Auto-refresh Removal**: Removed disruptive timers from content pages --- make_post_sell/static/css/common.css | 76 ++++++++++++-- .../templates/comments/edit_comment.j2 | 50 +++++----- .../templates/comments/reply_comment.j2 | 7 +- make_post_sell/templates/content.j2 | 11 +-- make_post_sell/templates/product.j2 | 99 ++++++++----------- make_post_sell/templates/snippets/comments.j2 | 35 ++++--- make_post_sell/views/comment.py | 34 ++++++- 7 files changed, 186 insertions(+), 126 deletions(-) diff --git a/make_post_sell/static/css/common.css b/make_post_sell/static/css/common.css index f65676c..03c3975 100644 --- a/make_post_sell/static/css/common.css +++ b/make_post_sell/static/css/common.css @@ -299,6 +299,49 @@ button.mps-button-red { opacity: 0.8; } +.mps-cancel-button, +.mps-comment-form-button { + display: inline-block; + padding: 6px 12px; + border: 1px solid #ccc; + border-radius: 4px; + background: #f9f9f9; + color: #333; + font-size: 14px; + cursor: pointer; + margin: 0; + text-decoration: none; + vertical-align: top; + box-sizing: border-box; +} + +.comment-actions { + margin-bottom: 42px; + display: grid; + grid-auto-flow: column; + grid-template-columns: repeat(auto-fit, max-content); + justify-content: start; + gap: 5px; +} + +.reply-link, +.edit-link, +.delete-link, +.moderate-link { + display: inline-block; + padding: 6px 12px; + border: 1px solid #ccc; + border-radius: 4px; + background: #f9f9f9; + color: #333; + font-size: 14px; + cursor: pointer; + margin: 0; + text-decoration: none; + vertical-align: top; + box-sizing: border-box; +} + a.product-edit-button { background-color: #98b6fa; } @@ -685,10 +728,28 @@ textarea.markup-editor-textarea { @media (max-width: 800px) { /* the two-column is stacked by default. */ section.two-column { + display: grid; + grid-template-columns: 1fr; max-width: 600px; margin-left: auto; margin-right: auto; } + + /* On mobile, show price section before description */ + section.product-left { + order: 2; + } + + section.product-right { + order: 1; + width: 100%; + } + + /* Ensure buttons get full width on mobile */ + section.product-right .mps-button { + width: 100%; + min-width: 100%; + } /* the cart-grid is stacked by default. */ section.cart-grid { @@ -725,13 +786,14 @@ textarea.markup-editor-textarea { /* We have the room to really break into 2 columns */ section.two-column { display: grid; - grid-template-columns: 1fr 1fr; - grid-auto-columns: max-content; - grid-auto-flow: dense; - gap: 0px 60px; - padding-left: 60px; - padding-right: 60px; - justify-items: center; + grid-template-columns: 2fr 1fr; + gap: 40px; + max-width: 1200px; + margin-left: auto; + margin-right: auto; + padding-left: 40px; + padding-right: 40px; + align-items: start; } /* if we have room, break markup-editor into 2 columns */ diff --git a/make_post_sell/templates/comments/edit_comment.j2 b/make_post_sell/templates/comments/edit_comment.j2 index 63e03ca..20f16f3 100644 --- a/make_post_sell/templates/comments/edit_comment.j2 +++ b/make_post_sell/templates/comments/edit_comment.j2 @@ -1,29 +1,31 @@ {% extends "base.j2" -%} -{% block content -%} - -
    -
    - -

    Edit Comment

    +{% block title %}Edit Comment{% endblock %} +{% block content %} +
    +

    Edit Comment

    + +
    +
    + {{ comment.user.name if comment.user else "Anonymous" }} + {{ comment.ago_string }} +
    +
    + {{ comment.data_html | safe }} +
    +
    +
    - - - - -
    -
    - - - Cancel - -
    -
    - +
    + + +
    + +
    + Cancel + +
    - -
    -
    - -{%- endblock -%} \ No newline at end of file + +{% endblock %} \ No newline at end of file diff --git a/make_post_sell/templates/comments/reply_comment.j2 b/make_post_sell/templates/comments/reply_comment.j2 index 2dea33a..1970cbc 100644 --- a/make_post_sell/templates/comments/reply_comment.j2 +++ b/make_post_sell/templates/comments/reply_comment.j2 @@ -19,13 +19,12 @@
    - - You can use Markdown formatting. +
    - - Cancel + Cancel +
    diff --git a/make_post_sell/templates/content.j2 b/make_post_sell/templates/content.j2 index 93cbb81..da582c1 100644 --- a/make_post_sell/templates/content.j2 +++ b/make_post_sell/templates/content.j2 @@ -17,16 +17,7 @@ {%- endif %} - {% if product.has_product_file and signed_get_object_url is not none %} - - {% endif %} + {# Removed auto-refresh timer - better UX to let download links expire than interrupt reading #} {%- endblock append_to_head_tag_section -%} diff --git a/make_post_sell/templates/product.j2 b/make_post_sell/templates/product.j2 index 388e502..2d96609 100644 --- a/make_post_sell/templates/product.j2 +++ b/make_post_sell/templates/product.j2 @@ -17,16 +17,7 @@ {%- endif %} - {% if product.has_product_file and signed_get_object_url is not none %} - - {% endif %} + {# Removed auto-refresh timer - better UX to let download links expire than interrupt reading #} {%- endblock append_to_head_tag_section -%} {%- block call_to_action -%} @@ -41,11 +32,7 @@
    -

    {{ product.title }}

    -

    - sold by - {{ product.shop.name }} -

    +

    {{ product.title }} sold by {{ product.shop.name }}

    {% if "thumbnail1" in product.extensions %} @@ -59,14 +46,47 @@ {% endif %} {% endfor %} +
    +
    + + Description +
    {{ product.description_html | safe }}
    + +
    +
    + + {% if product.is_bundle %} + {% if product.products_in_this_bundle %} + Bundle Contents +
      + {% for p in product.products_in_this_bundle %} +
    • + {{p.title}} +
    • + {% endfor %} +
    + {% endif %} + {% elif product.has_product_file %} + File Type +
    + {{ product.get_content_type("product") }} {{ product_size }} +
    + Please make sure you have an application to open this file type. + {% endif %} + +
    +
    + + + {% include 'snippets/comments.j2' %} + +
    + Back to shop +
    -
    -
    -
    -

    ${{ '{:,.2f}'.format(product.price) }}

    @@ -132,46 +152,5 @@
    -
    - -
    - - Description -
    {{ product.description_html | safe }}
    - -
    -
    - - {% if product.is_bundle %} - - {% if product.products_in_this_bundle %} -
      - {% for p in product.products_in_this_bundle %} -
    • - {{p.title}} -
    • - {% endfor %} -
    - {% endif %} - - {% elif product.has_product_file %} - File Type -
    - {{ product.get_content_type("product") }} {{ product_size }} -
    - Please make sure you have an application to open this file type. - - {% endif %} - -
    -
    - - - {% include 'snippets/comments.j2' %} - -
    - Back to shop - -
    {%- endblock -%} diff --git a/make_post_sell/templates/snippets/comments.j2 b/make_post_sell/templates/snippets/comments.j2 index cf37b92..5eeb784 100644 --- a/make_post_sell/templates/snippets/comments.j2 +++ b/make_post_sell/templates/snippets/comments.j2 @@ -17,30 +17,29 @@
    - {% if request.user.authenticated and not comment.is_locked %} - {% set can_comment, error_msg = comment.can_user_comment(request.user, shop) %} - {% if can_comment %} - Reply + {% if request.user.authenticated and (request.user.id == comment.user_id or comment.can_user_moderate(request.user, shop)) %} + + + {% endif %} + + {% if request.user.authenticated and comment.can_user_moderate(request.user, shop) %} + {% if comment.approved %} + + + {% else %} + + {% endif %} {% endif %} {% if request.user.authenticated and (request.user.id == comment.user_id or comment.can_user_moderate(request.user, shop)) %} Edit - -
    - -
    {% endif %} - {% if request.user.authenticated and comment.can_user_moderate(request.user, shop) %} - {% if comment.approved %} -
    - -
    - {% else %} -
    - -
    + {% if request.user.authenticated and not comment.is_locked %} + {% set can_comment, error_msg = comment.can_user_comment(request.user, shop) %} + {% if can_comment %} + Reply {% endif %} {% endif %}
    @@ -84,7 +83,7 @@
    - +
    diff --git a/make_post_sell/views/comment.py b/make_post_sell/views/comment.py index 4a2ce6e..59e2fba 100644 --- a/make_post_sell/views/comment.py +++ b/make_post_sell/views/comment.py @@ -268,11 +268,23 @@ def comment_delete(request): product_url = comment.product.absolute_url(request) + # Determine which comment to anchor to after deletion + if comment.parent_id: + # For replies, anchor to the parent comment + anchor_comment_id = comment.parent_id + else: + # For root comments, just go to the product page + anchor_comment_id = None + # Soft delete using disable method comment.disable() request.session.flash(("Comment deleted successfully", "success")) - return HTTPFound(location=product_url) + + if anchor_comment_id: + return HTTPFound(location=f"{product_url}#comment-{anchor_comment_id}") + else: + return HTTPFound(location=product_url) @view_config(route_name="comment_approve", request_method="POST") @@ -294,7 +306,15 @@ def comment_approve(request): comment.stamp_updated_timestamp() request.session.flash(("Comment approved", "success")) - return HTTPFound(location=get_referer_or_home(request)) + + # Determine which comment to anchor to after approval + product_url = comment.product.absolute_url(request) + if comment.parent_id: + # For replies, anchor to the parent comment + return HTTPFound(location=f"{product_url}#comment-{comment.parent_id}") + else: + # For root comments, anchor to the comment itself + return HTTPFound(location=f"{product_url}#comment-{comment.id}") @view_config(route_name="comment_unapprove", request_method="POST") @@ -316,7 +336,15 @@ def comment_unapprove(request): comment.stamp_updated_timestamp() request.session.flash(("Comment unapproved", "success")) - return HTTPFound(location=get_referer_or_home(request)) + + # Determine which comment to anchor to after unapproval + product_url = comment.product.absolute_url(request) + if comment.parent_id: + # For replies, anchor to the parent comment + return HTTPFound(location=f"{product_url}#comment-{comment.parent_id}") + else: + # For root comments, anchor to the comment itself + return HTTPFound(location=f"{product_url}#comment-{comment.id}") @view_config(route_name="comment_undelete", request_method="POST") From c218ec707308b09fa439b6e3cbf5a0d53a26925e Mon Sep 17 00:00:00 2001 From: Russell Ballestrini Date: Sat, 4 Oct 2025 12:02:04 -0400 Subject: [PATCH 009/687] Fix mobile layout ordering for product and content pages - **Mobile Layout Fix**: Restructured product/content templates to use direct grid children - **Grid Areas**: Added proper grid-template-areas for desktop layout - **Mobile Ordering**: Purchase/download buttons now appear after images on mobile - **Template Structure**: Split sections into product-images, product-right, product-description, product-comments - **CSS Grid**: Unified layout system using order properties for mobile and grid areas for desktop - **UX Improvement**: Logical mobile flow - images, purchase, description, comments --- make_post_sell/static/css/common.css | 36 ++++++++++-- make_post_sell/templates/content.j2 | 72 +++++++++++------------- make_post_sell/templates/product.j2 | 84 ++++++++++++++-------------- 3 files changed, 108 insertions(+), 84 deletions(-) diff --git a/make_post_sell/static/css/common.css b/make_post_sell/static/css/common.css index 03c3975..7441b55 100644 --- a/make_post_sell/static/css/common.css +++ b/make_post_sell/static/css/common.css @@ -735,16 +735,24 @@ textarea.markup-editor-textarea { margin-right: auto; } - /* On mobile, show price section before description */ - section.product-left { - order: 2; + /* On mobile, reorder to put purchase section after images */ + .product-images { + order: 1; } section.product-right { - order: 1; + order: 2; width: 100%; } + .product-description { + order: 3; + } + + .product-comments { + order: 4; + } + /* Ensure buttons get full width on mobile */ section.product-right .mps-button { width: 100%; @@ -787,6 +795,10 @@ textarea.markup-editor-textarea { section.two-column { display: grid; grid-template-columns: 2fr 1fr; + grid-template-areas: + "images purchase" + "description purchase" + "comments purchase"; gap: 40px; max-width: 1200px; margin-left: auto; @@ -795,6 +807,22 @@ textarea.markup-editor-textarea { padding-right: 40px; align-items: start; } + + .product-images { + grid-area: images; + } + + section.product-right { + grid-area: purchase; + } + + .product-description { + grid-area: description; + } + + .product-comments { + grid-area: comments; + } /* if we have room, break markup-editor into 2 columns */ div.markup-editor { diff --git a/make_post_sell/templates/content.j2 b/make_post_sell/templates/content.j2 index da582c1..c9b0148 100644 --- a/make_post_sell/templates/content.j2 +++ b/make_post_sell/templates/content.j2 @@ -32,14 +32,12 @@
    -
    - +
    {% if "thumbnail1" in product.extensions %} {% if product.extensions["product"] in ["mp3", "mp4", "wav", "flac", "aac", "ogg", "wma", "m4a", "avi", "mov", "wmv", "flv", "mkv", "webm", "m4v"] %}
    click ▶ to play!
    @@ -50,49 +48,45 @@
    {% endif %} -

    {{ product.title }}

    -

    - uploaded to {{ product.shop.name }} -

    - -
    - - Description -
    {{ product.description_html | safe }}
    - -
    -
    - - {% if product.has_product_file %} - File Type -
    - {{ product.get_content_type("product") }} {{ product_size }} -
    - {% endif %} - -
    +

    {{ product.title }} uploaded to {{ product.shop.name }}

    +
    - - {% if product.has_product_file %} -
    - {% if signed_get_object_url is not none %} - ⭳ Download + {% if product.has_product_file %} +
    + {% if signed_get_object_url is not none %} + ⭳ Download + {% endif %} +
    {% endif %} -
    - {% endif %} -
    -
    +
    +
    -
    - -
    - - - {% include 'snippets/comments.j2' %} + Description +
    {{ product.description_html | safe }}
    + +
    +
    + + {% if product.has_product_file %} + File Type +
    + {{ product.get_content_type("product") }} {{ product_size }} +
    + {% endif %} +
    + +
    +
    +
    + + + {% include 'snippets/comments.j2' %} +
    + {%- endblock -%} diff --git a/make_post_sell/templates/product.j2 b/make_post_sell/templates/product.j2 index 2d96609..032d3f2 100644 --- a/make_post_sell/templates/product.j2 +++ b/make_post_sell/templates/product.j2 @@ -30,8 +30,7 @@
    -
    - +

    {{ product.title }} sold by {{ product.shop.name }}

    {% if "thumbnail1" in product.extensions %} @@ -45,45 +44,7 @@ {% endif %} {% endfor %} - -
    -
    - - Description -
    {{ product.description_html | safe }}
    - -
    -
    - - {% if product.is_bundle %} - {% if product.products_in_this_bundle %} - Bundle Contents -
      - {% for p in product.products_in_this_bundle %} -
    • - {{p.title}} -
    • - {% endfor %} -
    - {% endif %} - {% elif product.has_product_file %} - File Type -
    - {{ product.get_content_type("product") }} {{ product_size }} -
    - Please make sure you have an application to open this file type. - {% endif %} - -
    -
    - - - {% include 'snippets/comments.j2' %} - -
    - Back to shop - -
    +
    @@ -150,6 +111,47 @@
    +
    +
    +
    + + Description +
    {{ product.description_html | safe }}
    + +
    +
    + + {% if product.is_bundle %} + {% if product.products_in_this_bundle %} + Bundle Contents +
      + {% for p in product.products_in_this_bundle %} +
    • + {{p.title}} +
    • + {% endfor %} +
    + {% endif %} + {% elif product.has_product_file %} + File Type +
    + {{ product.get_content_type("product") }} {{ product_size }} +
    + Please make sure you have an application to open this file type. + {% endif %} +
    + +
    +
    +
    + + + {% include 'snippets/comments.j2' %} + +
    + Back to shop +
    +
    From fd3ffc26f076172abefe4193e26782d274ac7c1c Mon Sep 17 00:00:00 2001 From: Russell Ballestrini Date: Sat, 4 Oct 2025 12:26:24 -0400 Subject: [PATCH 010/687] Improve shop settings crypto wallet and Stripe configuration UX MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Move crypto wallet verification disclaimer to top of section for better visibility - Remove redundant Dogecoin address display message - Ensure crypto wallet checkbox is always unchecked on page refresh - Update disclaimer to use plural form for multiple wallet addresses - Fix checkbox label alignment to display inline - Update Stripe toggle label to be more descriptive - Add JavaScript progressive enhancement for Stripe API key fields - Ensure both crypto and Stripe forms are hidden by default with graceful degradation 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- make_post_sell/templates/shop_settings.j2 | 80 ++++++++++++++++------- make_post_sell/views/shop.py | 5 ++ 2 files changed, 60 insertions(+), 25 deletions(-) diff --git a/make_post_sell/templates/shop_settings.j2 b/make_post_sell/templates/shop_settings.j2 index f2ee35b..fa941c2 100644 --- a/make_post_sell/templates/shop_settings.j2 +++ b/make_post_sell/templates/shop_settings.j2 @@ -133,7 +133,7 @@
    - +
    @@ -261,7 +261,23 @@
    + + + + +
    +
    + + + Note: We cannot verify you control these addresses. Please double-check they're correct! + Consider sending a small test amount from another wallet to verify. + + +
    +
    +
    +

    Monero (XMR) Configuration

    @@ -289,11 +305,6 @@

    ✓ Monero wallet configured and ready to accept payments -
    - - Note: We cannot verify you control this address. Please double-check it's correct! - Consider sending a small test amount from another wallet to verify. - {% else %}

    @@ -324,17 +335,8 @@
    - -
    - - -
    -
    -{% endif %} - -{% if request.dogecoin_enabled %} -
    -
    + +

    Dogecoin (DOGE) Configuration 🐕

    @@ -362,12 +364,6 @@

    ✓ Dogecoin wallet configured and ready to accept payments -
    -
    - - Your shop can now accept Dogecoin payments! Funds will be automatically swept to:
    - {{ doge_processor.sweep_to_address }} -
    {% else %}

    @@ -403,6 +399,8 @@
    + +
    @@ -410,8 +408,6 @@

    {% endif %} - -

    @@ -603,4 +599,38 @@ Existing sales honored for download buy purchasers.
    + + {%- endblock -%} diff --git a/make_post_sell/views/shop.py b/make_post_sell/views/shop.py index a4aaf49..1241be2 100644 --- a/make_post_sell/views/shop.py +++ b/make_post_sell/views/shop.py @@ -558,6 +558,11 @@ def shop_settings(request): shop.stripe_enabled = False request.session.flash(("Stripe payments disabled", "success")) + # Handle re-enable action - if stripe is disabled and we're submitting stripe settings + elif not shop.stripe_enabled: + shop.stripe_enabled = True + request.session.flash(("Stripe payments re-enabled", "success")) + elif stripe_public_api_key != shop.stripe_public_api_key: if stripe_public_api_key.startswith("pk_"): if not stripe_test_mode and "_test_" in stripe_public_api_key: From baa8dffc02496f8cf4579730a5700129be4dc244 Mon Sep 17 00:00:00 2001 From: Russell Ballestrini Date: Sat, 4 Oct 2025 12:37:32 -0400 Subject: [PATCH 011/687] Enhance payment settings UX with consistent styling and persistent state MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add new payment-toggle-button CSS class derived from comment button styles - Apply consistent styling to all Stripe, Monero, and Dogecoin disable/re-enable buttons - Consolidate crypto wallet disclaimers into single always-visible section - Update help text to clarify re-enabling payments allows address updates - Implement localStorage persistence for both crypto and Stripe toggle states - Ensure graceful degradation for non-JavaScript users - Remove redundant disclaimer text from individual currency forms 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- make_post_sell/static/css/common.css | 27 ++++++++++ make_post_sell/templates/shop_settings.j2 | 61 +++++++++++++++-------- 2 files changed, 67 insertions(+), 21 deletions(-) diff --git a/make_post_sell/static/css/common.css b/make_post_sell/static/css/common.css index 7441b55..f5aea46 100644 --- a/make_post_sell/static/css/common.css +++ b/make_post_sell/static/css/common.css @@ -342,6 +342,33 @@ button.mps-button-red { box-sizing: border-box; } +.payment-toggle-button { + display: inline-block; + padding: 6px 12px; + border: 1px solid #ccc; + border-radius: 4px; + background: #f9f9f9; + color: #333; + font-size: 14px; + cursor: pointer; + margin: 0; + text-decoration: none; + vertical-align: top; + box-sizing: border-box; +} + +.payment-toggle-button.enable { + background: #4a4; + color: white; + border-color: #4a4; +} + +.payment-toggle-button.disable { + background: #d44; + color: white; + border-color: #d44; +} + a.product-edit-button { background-color: #98b6fa; } diff --git a/make_post_sell/templates/shop_settings.j2 b/make_post_sell/templates/shop_settings.j2 index fa941c2..a121e4b 100644 --- a/make_post_sell/templates/shop_settings.j2 +++ b/make_post_sell/templates/shop_settings.j2 @@ -170,9 +170,9 @@ {% if request.shop.stripe_enabled %} - + {% else %} - + Enter your API keys to re-enable Stripe payments {% endif %} @@ -271,6 +271,8 @@ Note: We cannot verify you control these addresses. Please double-check they're correct! Consider sending a small test amount from another wallet to verify. +
    + Once set, cold wallet addresses cannot be removed, only replaced with new addresses.

    @@ -297,7 +299,6 @@
    Configure your cold wallet address where this shop's Monero funds will be swept. This is required to accept Monero payments. - {% if xmr_processor and xmr_processor.sweep_to_address %}
    Note: Once set, the cold wallet address cannot be removed, only replaced with a new address.{% endif %}
    {% if xmr_processor %} @@ -322,10 +323,10 @@ {% if xmr_processor %} {% if xmr_processor.enabled %} - + {% else %} - - Enter your cold wallet address to re-enable Monero payments + + Re-enable Monero payments to update your cold wallet address {% endif %} {% else %} @@ -356,7 +357,6 @@
    Configure your cold wallet address where this shop's Dogecoin funds will be swept. This is required to accept Dogecoin payments. - {% if doge_processor and doge_processor.sweep_to_address %}
    Note: Once set, the cold wallet address cannot be removed, only replaced with a new address.{% endif %}
    {% if doge_processor %} @@ -386,10 +386,10 @@ {% if doge_processor %} {% if doge_processor.enabled %} - + {% else %} - - Enter your cold wallet address to re-enable Dogecoin payments + + Re-enable Dogecoin payments to update your cold wallet address {% endif %} {% else %} @@ -600,24 +600,23 @@ Existing sales honored for download buy purchasers. {%- endblock -%} From fde7e2b2d07c60f88cdb6c59c063c148456488f8 Mon Sep 17 00:00:00 2001 From: Russell Ballestrini Date: Sat, 4 Oct 2025 12:49:37 -0400 Subject: [PATCH 012/687] Fix CSS image sizing specificity issue MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Change global img rule from width: 100% to max-width: 100% to allow more specific image sizing rules to work properly. This prevents the global rule from overriding thumbnail sizes, icons, and other specific image dimensions while maintaining responsive behavior. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- make_post_sell/static/css/common.css | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/make_post_sell/static/css/common.css b/make_post_sell/static/css/common.css index f5aea46..e337212 100644 --- a/make_post_sell/static/css/common.css +++ b/make_post_sell/static/css/common.css @@ -236,7 +236,8 @@ section.logo{ } img { - width: 100%; + max-width: 100%; + height: auto; } img.logo { From 3fedfeb84942e05303f97608b9eccad02027ab97 Mon Sep 17 00:00:00 2001 From: Russell Ballestrini Date: Sat, 4 Oct 2025 13:02:07 -0400 Subject: [PATCH 013/687] Update state machine documentation to use dash-separated naming - Convert all state names from underscore to dash format in markdown documentation - Ensures consistency across all documentation formats (dot, svg, markdown) - All payment states now use dashes: confirmed-complete, underpaid-refunded, etc. - Maintains consistency with the source dot file which is the canonical reference This completes the documentation naming convention standardization. --- Makefile | 7 +- docs/crypto-payments-state-machine.md | 122 +++--- docs/state-machine.dot | 90 ++-- docs/state-machine.dot.svg | 573 +++++++++++++------------- 4 files changed, 405 insertions(+), 387 deletions(-) diff --git a/Makefile b/Makefile index edfa004..6f47e4d 100644 --- a/Makefile +++ b/Makefile @@ -68,13 +68,18 @@ help: @echo " make sweep-all-doge - Sweep ALL Dogecoin wallet funds to cold storage (dust collection)" @echo " make monero-transactions - View recent wallet transactions" @echo "" + @echo "DOCUMENTATION:" + @echo " make docs/state-machine.dot.svg - Generate state machine diagram from .dot file" + @echo "" @echo "CLEANUP:" @echo " make clean - Remove virtual environment" @echo "" @echo "For more info, see README.md and CRYPTO.rst" -docs/state-machine.dot.svg: +docs/state-machine.dot.svg: docs/state-machine.dot + @echo "Generating state machine diagram from docs/state-machine.dot..." dot -Tsvg docs/state-machine.dot -o docs/state-machine.dot.svg + @echo "✓ Generated docs/state-machine.dot.svg" # ----------------------------------------------------------------------------- # Environment Setup Targets diff --git a/docs/crypto-payments-state-machine.md b/docs/crypto-payments-state-machine.md index 19a9fab..d5003e3 100644 --- a/docs/crypto-payments-state-machine.md +++ b/docs/crypto-payments-state-machine.md @@ -7,8 +7,8 @@ This document visualizes the complete state machine for cryptocurrency payments ```mermaid stateDiagram-v2 [*] --> pending - [*] --> doublepay_refunded : Duplicate payment detected - [*] --> latepay_refunded: Late payment detected + [*] --> doublepay-refunded : Duplicate payment detected + [*] --> latepay-refunded: Late payment detected %% Main payment flow pending --> received : Payment detected in mempool @@ -18,47 +18,47 @@ stateDiagram-v2 %% From received state - multiple possible outcomes %% NOTE: received payments CANNOT expire (detected in mempool, confirmations tracking) received --> confirmed : Sufficient payment + confirmations - received --> confirmed_overpay : Overpayment detected - received --> underpaid_refunded : Underpayment detected - received --> out_of_stock_refunded : Product unavailable + received --> confirmed-overpay : Overpayment detected + received --> underpaid-refunded : Underpayment detected + received --> out-of-stock-refunded : Product unavailable %% Successful payment paths - confirmed --> confirmed_complete : Swept to cold storage - confirmed_complete --> [*] : ✓ Terminal Success + confirmed --> confirmed-complete : Swept to cold storage + confirmed-complete --> [*] : ✓ Terminal Success %% Overpayment refund flow - confirmed_overpay --> confirmed_overpay_refunded : Initiate refund - confirmed_overpay_refunded --> confirmed_overpay_refunded_complete : Refund confirmed - confirmed_overpay_refunded --> confirmed_overpay_not_refunded : No refund wallet configured - confirmed_overpay_refunded_complete --> [*] : ✓ Terminal Success - confirmed_overpay_not_refunded --> [*] : ✓ Terminal Success (Not Refunded) + confirmed-overpay --> confirmed-overpay-refunded : Initiate refund + confirmed-overpay-refunded --> confirmed-overpay-refunded-complete : Refund confirmed + confirmed-overpay-refunded --> confirmed-overpay-not-refunded : No refund wallet configured + confirmed-overpay-refunded-complete --> [*] : ✓ Terminal Success + confirmed-overpay-not-refunded --> [*] : ✓ Terminal Success (Not Refunded) %% Expired payment handling (terminal - late payments create new objects) expired --> [*] : ✓ Terminal Failed (Expired) %% Late payment objects (created separately for payments after expiration) - latepay_refunded --> latepay_refunded_complete : Refund confirmed - latepay_refunded --> latepay_not_refunded : No refund wallet configured - latepay_refunded_complete --> [*] : ✓ Terminal Failed (Refund Complete) - latepay_not_refunded --> [*] : ✓ Terminal Failed (Not Refunded) + latepay-refunded --> latepay-refunded-complete : Refund confirmed + latepay-refunded --> latepay-not-refunded : No refund wallet configured + latepay-refunded-complete --> [*] : ✓ Terminal Failed (Refund Complete) + latepay-not-refunded --> [*] : ✓ Terminal Failed (Not Refunded) %% Underpayment refund flow - underpaid_refunded --> underpaid_refunded_complete : Refund confirmed - underpaid_refunded --> underpaid_not_refunded : No refund wallet configured - underpaid_refunded_complete --> [*] : ✓ Terminal Failed (Refund Complete) - underpaid_not_refunded --> [*] : ✓ Terminal Failed (Not Refunded) + underpaid-refunded --> underpaid-refunded-complete : Refund confirmed + underpaid-refunded --> underpaid-not-refunded : No refund wallet configured + underpaid-refunded-complete --> [*] : ✓ Terminal Failed (Refund Complete) + underpaid-not-refunded --> [*] : ✓ Terminal Failed (Not Refunded) %% Out of stock refund flow - out_of_stock_refunded --> out_of_stock_refunded_complete : Refund confirmed - out_of_stock_refunded --> out_of_stock_not_refunded : No refund wallet configured - out_of_stock_refunded_complete --> [*] : ✓ Terminal Failed (Refund Complete) - out_of_stock_not_refunded --> [*] : ✓ Terminal Failed (Not Refunded) + out-of-stock-refunded --> out-of-stock-refunded-complete : Refund confirmed + out-of-stock-refunded --> out-of-stock-not-refunded : No refund wallet configured + out-of-stock-refunded-complete --> [*] : ✓ Terminal Failed (Refund Complete) + out-of-stock-not-refunded --> [*] : ✓ Terminal Failed (Not Refunded) %% Double payment refund flow - doublepay_refunded --> doublepay_refunded_complete : Refund confirmed - doublepay_refunded --> doublepay_not_refunded : No refund wallet configured - doublepay_refunded_complete --> [*] : ✓ Terminal Failed (Refund Complete) - doublepay_not_refunded --> [*] : ✓ Terminal Failed (Not Refunded) + doublepay-refunded --> doublepay-refunded-complete : Refund confirmed + doublepay-refunded --> doublepay-not-refunded : No refund wallet configured + doublepay-refunded-complete --> [*] : ✓ Terminal Failed (Refund Complete) + doublepay-not-refunded --> [*] : ✓ Terminal Failed (Not Refunded) %% User cancellation (always terminal, only from pending) cancelled --> [*] : ✓ Terminal Failed (Cancelled) @@ -71,19 +71,19 @@ stateDiagram-v2 classDef processingState fill:#cce5ff,stroke:#004085,color:#004085 %% Successful payments (customer received product) - class confirmed,confirmed_complete,confirmed_overpay_refunded_complete,confirmed_overpay_not_refunded successState + class confirmed,confirmed-complete,confirmed-overpay-refunded-complete,confirmed-overpay-not-refunded successState %% Initial/waiting states (entry points that don't come from 'received') - class pending,latepay_refunded,doublepay_refunded initialWaitingState + class pending,latepay-refunded,doublepay-refunded initialWaitingState %% Active refund processing states - class confirmed_overpay_refunded,underpaid_refunded,out_of_stock_refunded refundState + class confirmed-overpay-refunded,underpaid-refunded,out-of-stock-refunded refundState %% Failed payments (customer did not receive product) - class expired,cancelled,latepay_refunded_complete,latepay_not_refunded,underpaid_refunded_complete,underpaid_not_refunded,out_of_stock_refunded_complete,out_of_stock_not_refunded,doublepay_refunded_complete,doublepay_not_refunded failedState + class expired,cancelled,latepay-refunded-complete,latepay-not-refunded,underpaid-refunded-complete,underpaid-not-refunded,out-of-stock-refunded-complete,out-of-stock-not-refunded,doublepay-refunded-complete,doublepay-not-refunded failedState %% Processing states - class received,confirmed_overpay processingState + class received,confirmed-overpay processingState ``` ## Semantic State Groups @@ -94,8 +94,8 @@ The state machine uses semantic groups to categorize states by business logic pu Entry point states that don't transition from `received` - they represent the start of payment flows: - **`pending`** - Initial state for new payment requests -- **`latepay_refunded`** - Initial state for late payment objects (payments received after expiration) -- **`doublepay_refunded`** - Initial state for duplicate payment objects (separate payment instances) +- **`latepay-refunded`** - Initial state for late payment objects (payments received after expiration) +- **`doublepay-refunded`** - Initial state for duplicate payment objects (separate payment instances) **Business Logic**: These states represent separate payment flows and are processed with Priority 0-2 depending on their nature. @@ -103,9 +103,9 @@ Entry point states that don't transition from `received` - they represent the st Customer received their product - invoices are preserved: - **`confirmed`** - Normal successful payment (exact amount, confirmed) -- **`confirmed_complete`** - Confirmed payment that has been swept to cold storage -- **`confirmed_overpay_refunded_complete`** - Overpaid, customer got product + refund -- **`confirmed_overpay_not_refunded`** - Overpaid, customer got product, no refund wallet configured +- **`confirmed-complete`** - Confirmed payment that has been swept to cold storage +- **`confirmed-overpay-refunded-complete`** - Overpaid, customer got product + refund +- **`confirmed-overpay-not-refunded`** - Overpaid, customer got product, no refund wallet configured **Business Logic**: `is_successful_payment() = True`, `should_keep_invoice() = True` @@ -114,18 +114,18 @@ Customer did not receive product - invoices are deleted: - **`expired`** - Payment window expired before any blockchain detection - **`cancelled`** - User cancelled payment (only from pending) -- **`*_refunded_complete`** - Failed payments with completed refunds -- **`*_not_refunded`** - Failed payments with no refund wallet configured +- **`*-refunded-complete`** - Failed payments with completed refunds +- **`*-not-refunded`** - Failed payments with no refund wallet configured **Business Logic**: `is_failed_payment() = True`, `should_keep_invoice() = False` ### 🟡 **Refund Processing States** (Yellow) Active refund workflows - intermediate states: -- **`confirmed_overpay_refunded`** - Overpayment refund in progress (customer got product) -- **`underpaid_refunded`** - Underpayment refund in progress -- **`out_of_stock_refunded`** - Out of stock refund in progress -- **Note**: `latepay_refunded` and `doublepay_refunded` are Initial/Waiting states, not regular refund processing +- **`confirmed-overpay-refunded`** - Overpayment refund in progress (customer got product) +- **`underpaid-refunded`** - Underpayment refund in progress +- **`out-of-stock-refunded`** - Out of stock refund in progress +- **Note**: `latepay-refunded` and `doublepay-refunded` are Initial/Waiting states, not regular refund processing **Business Logic**: Priority 0 processing (highest), actively monitored for confirmation @@ -133,7 +133,7 @@ Active refund workflows - intermediate states: Active payment processing states: - **`received`** - Payment detected on blockchain, being processed -- **`confirmed_overpay`** - Overpayment confirmed, deciding refund action +- **`confirmed-overpay`** - Overpayment confirmed, deciding refund action **Business Logic**: Priority 1-3 processing, confirmation monitoring @@ -153,8 +153,8 @@ All status constants use past-tense naming for consistency: ### **Rule 3: Entry Points vs Transitions** Some states are entry points for new payment objects, not transitions from existing payments: - `pending` - Entry point for new payments -- `latepay_refunded` - Entry point for late payment objects (created after expiration) -- `doublepay_refunded` - Entry point for duplicate payment objects +- `latepay-refunded` - Entry point for late payment objects (created after expiration) +- `doublepay-refunded` - Entry point for duplicate payment objects ### **Rule 4: Invoice Preservation Logic** ```python @@ -168,7 +168,7 @@ should_keep_invoice() = is_successful_payment() The crypto watcher processes payments by priority to ensure proper fund flow and customer service: ### **Priority 0 (Highest): Customer Refunds** -- `doublepay_refunded`, `latepay_refunded`, `underpaid_refunded`, `out_of_stock_refunded` +- `doublepay-refunded`, `latepay-refunded`, `underpaid-refunded`, `out-of-stock-refunded` - **Rationale**: Customer service is highest priority ### **Priority 1: New Incoming Payments** @@ -180,50 +180,50 @@ The crypto watcher processes payments by priority to ensure proper fund flow and - **Rationale**: General processing tasks ### **Priority 3: Auto-Sweep to Shop Owner** -- `confirmed`, `confirmed_overpay` +- `confirmed`, `confirmed-overpay` - **Rationale**: Move confirmed funds to shop owner ### **Priority 4 (Lowest): Restocking Fee Sweeps** -- `*_refunded_complete` states +- `*-refunded-complete` states - **Rationale**: Most dangerous operation, requires high confirmations, done last ## Business Logic Flows ### **Normal Payment Flow** ``` -pending → received → confirmed → confirmed_complete ✅ +pending → received → confirmed → confirmed-complete ✅ ``` Customer pays exact amount, gets product, invoice kept, funds swept to cold storage. ### **Overpayment Flow** ``` -pending → received → confirmed_overpay → confirmed_overpay_refunded → confirmed_overpay_refunded_complete ✅ +pending → received → confirmed-overpay → confirmed-overpay-refunded → confirmed-overpay-refunded-complete ✅ ``` Customer overpays, gets product, gets refund, invoice kept. ### **Late Payment Flow** ``` Original: pending → expired ❌ -New object: latepay_refunded → latepay_refunded_complete ❌ +New object: latepay-refunded → latepay-refunded-complete ❌ ``` Original payment expires. Late payment creates new object, gets refunded, invoice deleted. ### **Underpayment Flow** ``` -pending → received → underpaid_refunded → underpaid_refunded_complete ❌ +pending → received → underpaid-refunded → underpaid-refunded-complete ❌ ``` Customer pays too little, gets refund, no product, invoice deleted. ### **Duplicate Payment Flow** ``` Original: pending → received → confirmed ✅ -Duplicate: doublepay_refunded → doublepay_refunded_complete ❌ +Duplicate: doublepay-refunded → doublepay-refunded-complete ❌ ``` First payment succeeds, duplicate creates separate object and gets refunded. ### **Out of Stock Flow** ``` -pending → received → out_of_stock_refunded → out_of_stock_refunded_complete ❌ +pending → received → out-of-stock-refunded → out-of-stock-refunded-complete ❌ ``` Product unavailable, customer gets refund, no product, invoice deleted. @@ -237,15 +237,15 @@ User cancels before payment detected, invoice deleted. **Successful Terminals** (keep invoice): - `confirmed` - Normal success (awaiting sweep) -- `confirmed_complete` - Normal success + swept to cold storage -- `confirmed_overpay_refunded_complete` - Overpaid + refunded -- `confirmed_overpay_not_refunded` - Overpaid, no refund wallet +- `confirmed-complete` - Normal success + swept to cold storage +- `confirmed-overpay-refunded-complete` - Overpaid + refunded +- `confirmed-overpay-not-refunded` - Overpaid, no refund wallet **Failed Terminals** (delete invoice): - `expired` - Never paid - `cancelled` - User cancelled -- `*_refunded_complete` - Failed + refunded -- `*_not_refunded` - Failed, no refund wallet +- `*-refunded-complete` - Failed + refunded +- `*-not-refunded` - Failed, no refund wallet ## State Transition Validation diff --git a/docs/state-machine.dot b/docs/state-machine.dot index a287170..8c35e25 100644 --- a/docs/state-machine.dot +++ b/docs/state-machine.dot @@ -4,61 +4,61 @@ digraph G { edge [penwidth=1.5, fontsize=10, fontname="Arial"]; "[*]" [shape=circle, label="", width=0.2, fillcolor=black, style=filled]; "pending" [fillcolor="#e7f3ff", fontcolor="#0056b3", color="#0056b3", fontsize=12]; - "doublepay_refunded" [fillcolor="#e7f3ff", fontcolor="#0056b3", color="#0056b3", fontsize=12]; - "latepay_refunded" [fillcolor="#e7f3ff", fontcolor="#0056b3", color="#0056b3", fontsize=12]; + "doublepay-refunded" [fillcolor="#e7f3ff", fontcolor="#0056b3", color="#0056b3", fontsize=12]; + "latepay-refunded" [fillcolor="#e7f3ff", fontcolor="#0056b3", color="#0056b3", fontsize=12]; "received" [fillcolor="#cce5ff", fontcolor="#004085", color="#004085", fontsize=12]; "expired" [fillcolor="#f8d7da", fontcolor="#721c24", color="#721c24", fontsize=12]; "cancelled" [fillcolor="#f8d7da", fontcolor="#721c24", color="#721c24", fontsize=12]; "confirmed" [fillcolor="#d4edda", fontcolor="#155724", color="#155724", fontsize=12]; - "confirmed_complete" [fillcolor="#d4edda", fontcolor="#155724", color="#155724", fontsize=12]; - "confirmed_overpay" [fillcolor="#cce5ff", fontcolor="#004085", color="#004085", fontsize=12]; - "underpaid_refunded" [fillcolor="#fff3cd", fontcolor="#856404", color="#856404", fontsize=12]; - "out_of_stock_refunded" [fillcolor="#fff3cd", fontcolor="#856404", color="#856404", fontsize=12]; - "confirmed_overpay_refunded" [fillcolor="#fff3cd", fontcolor="#856404", color="#856404", fontsize=12]; - "confirmed_overpay_refunded_complete" [fillcolor="#d4edda", fontcolor="#155724", color="#155724", fontsize=12]; - "confirmed_overpay_not_refunded" [fillcolor="#d4edda", fontcolor="#155724", color="#155724", fontsize=12]; - "latepay_refunded_complete" [fillcolor="#f8d7da", fontcolor="#721c24", color="#721c24", fontsize=12]; - "latepay_not_refunded" [fillcolor="#f8d7da", fontcolor="#721c24", color="#721c24", fontsize=12]; - "underpaid_refunded_complete" [fillcolor="#f8d7da", fontcolor="#721c24", color="#721c24", fontsize=12]; - "underpaid_not_refunded" [fillcolor="#f8d7da", fontcolor="#721c24", color="#721c24", fontsize=12]; - "out_of_stock_refunded_complete" [fillcolor="#f8d7da", fontcolor="#721c24", color="#721c24", fontsize=12]; - "out_of_stock_not_refunded" [fillcolor="#f8d7da", fontcolor="#721c24", color="#721c24", fontsize=12]; - "doublepay_refunded_complete" [fillcolor="#f8d7da", fontcolor="#721c24", color="#721c24", fontsize=12]; - "doublepay_not_refunded" [fillcolor="#f8d7da", fontcolor="#721c24", color="#721c24", fontsize=12]; + "confirmed-complete" [fillcolor="#d4edda", fontcolor="#155724", color="#155724", fontsize=12]; + "confirmed-overpay" [fillcolor="#cce5ff", fontcolor="#004085", color="#004085", fontsize=12]; + "underpaid-refunded" [fillcolor="#fff3cd", fontcolor="#856404", color="#856404", fontsize=12]; + "out-of-stock-refunded" [fillcolor="#fff3cd", fontcolor="#856404", color="#856404", fontsize=12]; + "confirmed-overpay-refunded" [fillcolor="#fff3cd", fontcolor="#856404", color="#856404", fontsize=12]; + "confirmed-overpay-refunded-complete" [fillcolor="#d4edda", fontcolor="#155724", color="#155724", fontsize=12]; + "confirmed-overpay-not-refunded" [fillcolor="#d4edda", fontcolor="#155724", color="#155724", fontsize=12]; + "latepay-refunded-complete" [fillcolor="#f8d7da", fontcolor="#721c24", color="#721c24", fontsize=12]; + "latepay-not-refunded" [fillcolor="#f8d7da", fontcolor="#721c24", color="#721c24", fontsize=12]; + "underpaid-refunded-complete" [fillcolor="#f8d7da", fontcolor="#721c24", color="#721c24", fontsize=12]; + "underpaid-not-refunded" [fillcolor="#f8d7da", fontcolor="#721c24", color="#721c24", fontsize=12]; + "out-of-stock-refunded-complete" [fillcolor="#f8d7da", fontcolor="#721c24", color="#721c24", fontsize=12]; + "out-of-stock-not-refunded" [fillcolor="#f8d7da", fontcolor="#721c24", color="#721c24", fontsize=12]; + "doublepay-refunded-complete" [fillcolor="#f8d7da", fontcolor="#721c24", color="#721c24", fontsize=12]; + "doublepay-not-refunded" [fillcolor="#f8d7da", fontcolor="#721c24", color="#721c24", fontsize=12]; "terminated" [shape=doubleoctagon, fillcolor="#e0e0e0", fontcolor="#333333", color="#333333", fontsize=12]; "[*]" -> "pending"; - "[*]" -> "doublepay_refunded" [label="Duplicate payment detected"]; - "[*]" -> "latepay_refunded" [label="Late payment detected"]; + "[*]" -> "doublepay-refunded" [label="Duplicate payment detected"]; + "[*]" -> "latepay-refunded" [label="Late payment detected"]; "pending" -> "received" [label="Payment detected in mempool"]; "pending" -> "expired" [label="Payment timeout (never received)"]; "pending" -> "cancelled" [label="User cancellation"]; "received" -> "confirmed" [label="Sufficient payment + confirmations"]; - "received" -> "confirmed_overpay" [label="Overpayment detected"]; - "received" -> "underpaid_refunded" [label="Underpayment detected"]; - "received" -> "out_of_stock_refunded" [label="Product unavailable"]; - "confirmed" -> "confirmed_complete" [label="Swept to cold storage"]; - "confirmed_complete" -> "terminated" [label="✓ Terminal Success"]; - "confirmed_overpay" -> "confirmed_overpay_refunded" [label="Initiate refund"]; - "confirmed_overpay_refunded" -> "confirmed_overpay_refunded_complete" [label="Refund confirmed"]; - "confirmed_overpay_refunded" -> "confirmed_overpay_not_refunded" [label="No refund wallet configured"]; - "confirmed_overpay_refunded_complete" -> "terminated" [label="✓ Terminal Success"]; - "confirmed_overpay_not_refunded" -> "terminated" [label="✓ Terminal Success (Not Refunded)"]; + "received" -> "confirmed-overpay" [label="Overpayment detected"]; + "received" -> "underpaid-refunded" [label="Underpayment detected"]; + "received" -> "out-of-stock-refunded" [label="Product unavailable"]; + "confirmed" -> "confirmed-complete" [label="Swept to cold storage"]; + "confirmed-complete" -> "terminated" [label="✓ Terminal Success"]; + "confirmed-overpay" -> "confirmed-overpay-refunded" [label="Initiate refund"]; + "confirmed-overpay-refunded" -> "confirmed-overpay-refunded-complete" [label="Refund confirmed"]; + "confirmed-overpay-refunded" -> "confirmed-overpay-not-refunded" [label="No refund wallet configured"]; + "confirmed-overpay-refunded-complete" -> "terminated" [label="✓ Terminal Success"]; + "confirmed-overpay-not-refunded" -> "terminated" [label="✓ Terminal Success (Not Refunded)"]; "expired" -> "terminated" [label="✓ Terminal Failed (Expired)"]; - "latepay_refunded" -> "latepay_refunded_complete" [label="Refund confirmed"]; - "latepay_refunded" -> "latepay_not_refunded" [label="No refund wallet configured"]; - "latepay_refunded_complete" -> "terminated" [label="✓ Terminal Failed (Refund Complete)"]; - "latepay_not_refunded" -> "terminated" [label="✓ Terminal Failed (Not Refunded)"]; - "underpaid_refunded" -> "underpaid_refunded_complete" [label="Refund confirmed"]; - "underpaid_refunded" -> "underpaid_not_refunded" [label="No refund wallet configured"]; - "underpaid_refunded_complete" -> "terminated" [label="✓ Terminal Failed (Refund Complete)"]; - "underpaid_not_refunded" -> "terminated" [label="✓ Terminal Failed (Not Refunded)"]; - "out_of_stock_refunded" -> "out_of_stock_refunded_complete" [label="Refund confirmed"]; - "out_of_stock_refunded" -> "out_of_stock_not_refunded" [label="No refund wallet configured"]; - "out_of_stock_refunded_complete" -> "terminated" [label="✓ Terminal Failed (Refund Complete)"]; - "out_of_stock_not_refunded" -> "terminated" [label="✓ Terminal Failed (Not Refunded)"]; - "doublepay_refunded" -> "doublepay_refunded_complete" [label="Refund confirmed"]; - "doublepay_refunded" -> "doublepay_not_refunded" [label="No refund wallet configured"]; - "doublepay_refunded_complete" -> "terminated" [label="✓ Terminal Failed (Refund Complete)"]; - "doublepay_not_refunded" -> "terminated" [label="✓ Terminal Failed (Not Refunded)"]; + "latepay-refunded" -> "latepay-refunded-complete" [label="Refund confirmed"]; + "latepay-refunded" -> "latepay-not-refunded" [label="No refund wallet configured"]; + "latepay-refunded-complete" -> "terminated" [label="✓ Terminal Failed (Refund Complete)"]; + "latepay-not-refunded" -> "terminated" [label="✓ Terminal Failed (Not Refunded)"]; + "underpaid-refunded" -> "underpaid-refunded-complete" [label="Refund confirmed"]; + "underpaid-refunded" -> "underpaid-not-refunded" [label="No refund wallet configured"]; + "underpaid-refunded-complete" -> "terminated" [label="✓ Terminal Failed (Refund Complete)"]; + "underpaid-not-refunded" -> "terminated" [label="✓ Terminal Failed (Not Refunded)"]; + "out-of-stock-refunded" -> "out-of-stock-refunded-complete" [label="Refund confirmed"]; + "out-of-stock-refunded" -> "out-of-stock-not-refunded" [label="No refund wallet configured"]; + "out-of-stock-refunded-complete" -> "terminated" [label="✓ Terminal Failed (Refund Complete)"]; + "out-of-stock-not-refunded" -> "terminated" [label="✓ Terminal Failed (Not Refunded)"]; + "doublepay-refunded" -> "doublepay-refunded-complete" [label="Refund confirmed"]; + "doublepay-refunded" -> "doublepay-not-refunded" [label="No refund wallet configured"]; + "doublepay-refunded-complete" -> "terminated" [label="✓ Terminal Failed (Refund Complete)"]; + "doublepay-not-refunded" -> "terminated" [label="✓ Terminal Failed (Not Refunded)"]; "cancelled" -> "terminated" [label="✓ Terminal Failed (Cancelled)"]; } diff --git a/docs/state-machine.dot.svg b/docs/state-machine.dot.svg index 5699780..ac4ce7e 100644 --- a/docs/state-machine.dot.svg +++ b/docs/state-machine.dot.svg @@ -4,385 +4,398 @@ - - + + G - + [*] - + pending - -pending + +pending [*]->pending - - + + - + -doublepay_refunded - -doublepay_refunded +doublepay-refunded + +doublepay-refunded - + -[*]->doublepay_refunded - - -Duplicate payment detected +[*]->doublepay-refunded + + +Duplicate payment detected - + -latepay_refunded - -latepay_refunded +latepay-refunded + +latepay-refunded - + -[*]->latepay_refunded - - -Late payment detected +[*]->latepay-refunded + + +Late payment detected received - -received + +received pending->received - - -Payment detected in mempool + + +Payment detected in mempool expired - -expired + +expired pending->expired - - -Payment timeout (never received) + + +Payment timeout (never received) cancelled - -cancelled + +cancelled pending->cancelled - - -User cancellation + + +User cancellation - - -doublepay_refunded_complete - -doublepay_refunded_complete - - - -doublepay_refunded->doublepay_refunded_complete - - -Refund confirmed - - + -doublepay_not_refunded - -doublepay_not_refunded +doublepay-refunded-complete + +doublepay-refunded-complete - + -doublepay_refunded->doublepay_not_refunded - - -No refund wallet configured +doublepay-refunded->doublepay-refunded-complete + + +Refund confirmed - - -latepay_refunded_complete - -latepay_refunded_complete + + +doublepay-not-refunded + +doublepay-not-refunded - - -latepay_refunded->latepay_refunded_complete - - -Refund confirmed + + +doublepay-refunded->doublepay-not-refunded + + +No refund wallet configured - + -latepay_not_refunded - -latepay_not_refunded +latepay-refunded-complete + +latepay-refunded-complete - + -latepay_refunded->latepay_not_refunded - - -No refund wallet configured +latepay-refunded->latepay-refunded-complete + + +Refund confirmed + + + +latepay-not-refunded + +latepay-not-refunded + + + +latepay-refunded->latepay-not-refunded + + +No refund wallet configured confirmed - -confirmed + +confirmed received->confirmed - - -Sufficient payment + confirmations + + +Sufficient payment + confirmations - - -confirmed_overpay - -confirmed_overpay - - - -received->confirmed_overpay - - -Overpayment detected - - + -underpaid_refunded - -underpaid_refunded +confirmed-overpay + +confirmed-overpay - - -received->underpaid_refunded - - -Underpayment detected + + +received->confirmed-overpay + + +Overpayment detected - + -out_of_stock_refunded - -out_of_stock_refunded +underpaid-refunded + +underpaid-refunded - + + +received->underpaid-refunded + + +Underpayment detected + + + +out-of-stock-refunded + +out-of-stock-refunded + + -received->out_of_stock_refunded - - -Product unavailable +received->out-of-stock-refunded + + +Product unavailable - + terminated - - -terminated + + +terminated - + expired->terminated - - -✓ Terminal Failed (Expired) + + +✓ Terminal Failed (Expired) - + cancelled->terminated - - -✓ Terminal Failed (Cancelled) + + +✓ Terminal Failed (Cancelled) - + + +confirmed-complete + +confirmed-complete + + -confirmed->terminated - - -✓ Terminal Success +confirmed->confirmed-complete + + +Swept to cold storage - - -confirmed_overpay_refunded - -confirmed_overpay_refunded - - + -confirmed_overpay->confirmed_overpay_refunded - - -Initiate refund +confirmed-complete->terminated + + +✓ Terminal Success - - -underpaid_refunded_complete - -underpaid_refunded_complete - - - -underpaid_refunded->underpaid_refunded_complete - - -Refund confirmed - - - -underpaid_not_refunded - -underpaid_not_refunded - - - -underpaid_refunded->underpaid_not_refunded - - -No refund wallet configured - - - -out_of_stock_refunded_complete - -out_of_stock_refunded_complete - - - -out_of_stock_refunded->out_of_stock_refunded_complete - - -Refund confirmed - - - -out_of_stock_not_refunded - -out_of_stock_not_refunded - - - -out_of_stock_refunded->out_of_stock_not_refunded - - -No refund wallet configured - - + -confirmed_overpay_refunded_complete - -confirmed_overpay_refunded_complete +confirmed-overpay-refunded + +confirmed-overpay-refunded - + -confirmed_overpay_refunded->confirmed_overpay_refunded_complete - - -Refund confirmed +confirmed-overpay->confirmed-overpay-refunded + + +Initiate refund - - -confirmed_overpay_not_refunded - -confirmed_overpay_not_refunded + + +underpaid-refunded-complete + +underpaid-refunded-complete - - -confirmed_overpay_refunded->confirmed_overpay_not_refunded - - -No refund wallet configured + + +underpaid-refunded->underpaid-refunded-complete + + +Refund confirmed - - -confirmed_overpay_refunded_complete->terminated - - -✓ Terminal Success + + +underpaid-not-refunded + +underpaid-not-refunded - - -confirmed_overpay_not_refunded->terminated - - -✓ Terminal Success (Not Refunded) - - - -latepay_refunded_complete->terminated - - -✓ Terminal Failed (Refund Complete) - - - -latepay_not_refunded->terminated - - -✓ Terminal Failed (Not Refunded) - - + -underpaid_refunded_complete->terminated - - -✓ Terminal Failed (Refund Complete) +underpaid-refunded->underpaid-not-refunded + + +No refund wallet configured - - -underpaid_not_refunded->terminated - - -✓ Terminal Failed (Not Refunded) + + +out-of-stock-refunded-complete + +out-of-stock-refunded-complete - + + +out-of-stock-refunded->out-of-stock-refunded-complete + + +Refund confirmed + + + +out-of-stock-not-refunded + +out-of-stock-not-refunded + + -out_of_stock_refunded_complete->terminated - - -✓ Terminal Failed (Refund Complete) +out-of-stock-refunded->out-of-stock-not-refunded + + +No refund wallet configured - + + +confirmed-overpay-refunded-complete + +confirmed-overpay-refunded-complete + + + +confirmed-overpay-refunded->confirmed-overpay-refunded-complete + + +Refund confirmed + + + +confirmed-overpay-not-refunded + +confirmed-overpay-not-refunded + + + +confirmed-overpay-refunded->confirmed-overpay-not-refunded + + +No refund wallet configured + + + +confirmed-overpay-refunded-complete->terminated + + +✓ Terminal Success + + + +confirmed-overpay-not-refunded->terminated + + +✓ Terminal Success (Not Refunded) + + + +latepay-refunded-complete->terminated + + +✓ Terminal Failed (Refund Complete) + + + +latepay-not-refunded->terminated + + +✓ Terminal Failed (Not Refunded) + + + +underpaid-refunded-complete->terminated + + +✓ Terminal Failed (Refund Complete) + + + +underpaid-not-refunded->terminated + + +✓ Terminal Failed (Not Refunded) + + -out_of_stock_not_refunded->terminated - - -✓ Terminal Failed (Not Refunded) +out-of-stock-refunded-complete->terminated + + +✓ Terminal Failed (Refund Complete) - - -doublepay_refunded_complete->terminated - - -✓ Terminal Failed (Refund Complete) + + +out-of-stock-not-refunded->terminated + + +✓ Terminal Failed (Not Refunded) - + -doublepay_not_refunded->terminated - - -✓ Terminal Failed (Not Refunded) +doublepay-refunded-complete->terminated + + +✓ Terminal Failed (Refund Complete) + + + +doublepay-not-refunded->terminated + + +✓ Terminal Failed (Not Refunded) From a3826d15c8a7302df5425c07c29a2975a3b2435a Mon Sep 17 00:00:00 2001 From: Russell Ballestrini Date: Sat, 4 Oct 2025 13:05:01 -0400 Subject: [PATCH 014/687] modified: setup.py --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 79dc222..43186b1 100644 --- a/setup.py +++ b/setup.py @@ -29,7 +29,7 @@ with open(os.path.join(here, "README.rst"), "r", encoding="utf-8") as f: setup( name="make_post_sell", - version="1.1.2", + version="1.1.3", description="Make Post Sell", long_description=long_description, classifiers=[ From 6b222bbc6b6b5c68cd157ce4e5072eca5e3c7d54 Mon Sep 17 00:00:00 2001 From: Russell Ballestrini Date: Sat, 4 Oct 2025 13:06:28 -0400 Subject: [PATCH 015/687] modified: .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 7803749..2550ca7 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ coverage.xml build/ dist/ data/ +data*/ src/ .tox/ nosetests.xml From 10e0e83adfb012eb61dac293abd9f8b7ab043eca Mon Sep 17 00:00:00 2001 From: Russell Ballestrini Date: Sat, 4 Oct 2025 13:20:45 -0400 Subject: [PATCH 016/687] Replace inline styles with CSS classes across template files - Add comprehensive CSS classes to common.css for status messages, layouts, and styling - Remove all inline styles from shop_settings.j2 (16 instances) - Remove all inline styles from crypto_checkout.j2 (25 instances) - Remove all inline styles from cart_checkout.j2 (18 instances) - Remove all inline styles from cart.j2 (16 instances) New CSS classes added: - Status indicators: .status-message, .success-indicator, .error-indicator - Layout helpers: .inline-label, .full-width-input, .disabled-input - Crypto checkout: .crypto-logo, .payment-grid, .payment-buttons, .status-box - Cart styles: .cart-float-right, .cart-shop-name, .cart-total-amount - Notice boxes: .success-notice, .warning-notice, .warning-banner This improves maintainability, consistency, and enables better theming support. All conditional styling preserved using dynamic CSS class application. --- make_post_sell/static/css/common.css | 249 ++++++++++++++++++++ make_post_sell/templates/cart.j2 | 32 +-- make_post_sell/templates/cart_checkout.j2 | 36 +-- make_post_sell/templates/crypto_checkout.j2 | 36 +-- make_post_sell/templates/shop_settings.j2 | 33 ++- 5 files changed, 317 insertions(+), 69 deletions(-) diff --git a/make_post_sell/static/css/common.css b/make_post_sell/static/css/common.css index e337212..0201cdf 100644 --- a/make_post_sell/static/css/common.css +++ b/make_post_sell/static/css/common.css @@ -1069,3 +1069,252 @@ div.message-ribbon { display: block; } /* hidden control area */ + +/* Status and message styling */ +.status-message { + color: #666; +} + +.success-indicator { + color: green; +} + +.error-indicator { + color: #d44; +} + +.note-text { + color: #666; +} + +.inline-label { + display: inline; +} + +.favicon-preview { + width: 32px; +} + +/* Input field styling */ +.full-width-input { + width: 100%; +} + +.disabled-input { + opacity: 0.5; + background-color: #f5f5f5; +} + +/* Crypto checkout styles */ +.crypto-logo { + width: 150px; + height: 150px; + margin-right: 15px; + vertical-align: middle; +} + +.payment-grid { + display: grid; + grid-template-columns: auto 1fr; + gap: 20px; + margin: 16px 0; + align-items: start; +} + +.crypto-address { + white-space: pre-wrap; + word-wrap: break-word; +} + +.payment-buttons { + display: grid; + grid-template-columns: 1fr 1fr 1fr; + gap: 8px; + margin: 8px 0; +} + +.payment-button-row-1 { + grid-row: 1; +} + +.payment-button-cancel { + grid-column: 1 / -1; + grid-row: 2; +} + +.status-box { + background: #f8f9fa; + border: 1px solid #dee2e6; + border-radius: 4px; + padding: 15px; + margin: 10px 0; +} + +.confirmations-hidden { + display: none; +} + +.qr-fallback { + width: 150px; + height: 150px; + background: #f0f0f0; + display: flex; + align-items: center; + justify-content: center; + font-size: 12px; + color: #666; + border: 1px solid #ddd; +} + +.success-notice { + background: #d1fae5; + border: 1px solid #10b981; + border-radius: 4px; + padding: 15px; + margin: 20px 0; +} + +.warning-notice { + background: #fef3c7; + border: 1px solid #f59e0b; + border-radius: 4px; + padding: 15px; + margin: 20px 0; +} + +.monospace-address { + font-family: monospace; + background: #f0f0f0; + padding: 8px; + border-radius: 3px; + word-break: break-all; +} + +.notice-list { + margin: 10px 0; +} + +.notice-footer { + margin-top: 10px; +} + +/* Cart checkout styles */ +.disabled-crypto-button { + background: #aaa !important; + cursor: not-allowed; +} + +.crypto-button-icon { + width: 32px; + height: 32px; + margin-right: 8px; + vertical-align: middle; +} + +.crypto-settings-link { + color: #f59e0b; + text-decoration: none; + display: block; + margin-top: 10px; + font-size: 14px; +} + +.warning-banner { + background: #fff3cd; + border: 1px solid #ffeaa7; + padding: 10px; + border-radius: 5px; + color: #856404; +} + +.warning-banner-alt { + background: #fff3cd; + border: 1px solid #ffc107; + border-radius: 4px; + padding: 15px; + margin: 20px 0; +} + +.warning-banner p { + margin: 0; + font-size: 14px; +} + +.warning-banner h3 { + margin-top: 0; +} + +.warning-banner-content { + margin-bottom: 15px; +} + +.pending-quote-item { + background: #f8f9fa; + border: 1px solid #dee2e6; + border-radius: 3px; + padding: 10px; + margin: 8px 0; +} + +.pending-quote-grid { + display: grid; + grid-template-columns: 1fr auto; + align-items: start; +} + +.pending-quote-status { + color: #666; + margin-left: 10px; +} + +.view-quote-button { + background: #17a2b8; + font-size: 12px; + padding: 5px 10px; +} + +.pending-quote-details { + font-size: 12px; + color: #666; + margin-top: 5px; +} + +/* Cart page styles */ +.cart-float-right { + float: right; +} + +.cart-shop-grid-span { + grid-column: span 3; +} + +.cart-shop-name { + font-size: 1.5em; + font-weight: bold; +} + +.cart-inline-form { + display: inline; +} + +.cart-update-button { + width: 80px; +} + +.cart-total-section { + text-align: right; +} + +.cart-total-grid-span { + text-align: right; + grid-column: span 3; +} + +.cart-total-amount { + font-size: 1.5em; + font-weight: bold; +} + +.cart-public-link { + font-size: 0.8em; +} diff --git a/make_post_sell/templates/cart.j2 b/make_post_sell/templates/cart.j2 index 7b2e160..8b6e784 100644 --- a/make_post_sell/templates/cart.j2 +++ b/make_post_sell/templates/cart.j2 @@ -31,7 +31,7 @@ {{ coupon.human_limit_per_customer }} -
    +
    -
    - {{ shop.name }} +
    + {{ shop.name }}
    {% for product, quantity in product_quantity_tuples %} @@ -110,22 +110,22 @@
    - + {% include "snippets/csrf.j2" %} quantity: - +
    -
    + {% include "snippets/csrf.j2" %}
    -
    +
    {{ "{:,}".format(product_quantity) }} x ${{ "{:,.2f}".format(product.price) }}
    ${{ "{:,.2f}".format(line_total) }} @@ -140,23 +140,23 @@
    {% if request.shop_location.local_pickup %} -
    +
    {% endif %} {% if request.shop_location.local_delivery %} -