PayPal:
- Document invasive KYC requirements (face scanning, government ID)
- Note that crypto is the privacy-preserving alternative
Adyen:
- Document integration approach (similar to Stripe)
- Include Python library usage, webhooks, credentials needed
- Status: not yet implemented
- Add stripe_payment_intent_id and stripe_charge_id columns to Invoice
- Store payment references during checkout for traceability
- Use idempotency key to prevent duplicate charges on retry
- Add Stripe webhook handler for payment_intent.succeeded, payment_failed,
charge.refunded, and charge.dispute.created events
- Consolidate PayPal webhooks into webhooks.py
- Add stripe.webhook_secret configuration for signature verification
Tests: 8 unit, 5 integration, 7 functional tests for Stripe functionality
The checkout POST now returns a 200 OK directly (or with different
redirect count), so update the test to:
1. Use a while loop to follow any number of redirects
2. Update assertion text from "Please enter your payment information."
to "Please confirm your order." to match current UI text
- Add paypal_order_id and paypal_capture_id columns to Invoice model
- Update migration to add columns to mps_invoice instead of creating separate table
- Remove PayPalPayment model (simpler architecture matching Stripe)
- Update paypal.py to store PayPal info directly on Invoice
- Update paypal_webhooks.py to query Invoice by paypal_order_id
- Update Invoice.payment_method property to detect PayPal payments
- Create docs/ directory and move CHANGES_PAYPAL.md into it
- Add CHANGELOG.rst with unreleased section for PayPal integration
- Update README.rst to mention PayPal alongside other payment methods
- Add vault parameters to order creation for saving payment methods
- Extract and store vault.id in PayPalUserShop after successful payment
- Add "Save PayPal" checkbox to checkout page
- Show saved status for returning customers
- Add PayPal management section to billing page
- Add disconnect PayPal functionality at /billing/disconnect-paypal
- Refunds are handled externally by shop owners via PayPal dashboard
- Platform does not track or process refunds
- Update documentation to reflect external refund handling
Documentation:
- Update CHANGES_PAYPAL.md
No database migrations required - uses existing PayPalUserShop columns:
- active_payment_token (stores vault.id)
- payer_id (stores PayPal payer ID)
EOF
)"
- Added vault parameters for saving PayPal payment methods
- Extract and store vault.id after successful payment
- Logs vault status (VAULTED vs APPROVED)
make_post_sell/views/billing.py
- Added paypal_user_shop to template context
- Added disconnect_paypal() view function
Implement CSS Grid Lanes (masonry layout) for product grids with graceful
fallback to standard CSS Grid for browsers that don't support it yet.
- Add grid_lanes_enabled column to Shop model (default: True)
- Add toggle in Shop Settings under Announcement Ribbon Settings
- Update product grid templates to conditionally apply grid-lanes-enabled class
- CSS uses @supports (display: grid-lanes) for progressive enhancement
- Mobile (160px min) and desktop (240px min) breakpoints supported
When enabled and browser supports Grid Lanes, products flow into a
waterfall/masonry layout where items fill the shortest column first,
creating a Pinterest-style layout that handles varying image heights.
Display a centered play button overlay on hero thumbnail images when
the product/content is a video file and the user has access to it.
Clicking opens the video in a new tab.
- Add CSS for .video-thumbnail-container and .video-play-overlay
- Support dark theme with appropriate contrast
- Show overlay on content.j2 for free video content
- Show overlay on product.j2 only when user has unlocked the product
Links shared to Matrix/Discord were showing the logo instead of thumbnail1
because twitter:card and twitter:image meta tags were missing. Added
complete Twitter card support (summary_large_image) plus og:url and
og:site_name for better Open Graph compliance.
- Move OTP code to top of email as large h1 element
- Increase font size to 3em for better readability
- Add letter spacing and bold styling for clarity
- Demote greeting text to regular paragraph
The test was failing due to SQLAlchemy DetachedInstanceError when
accessing object properties after transaction.manager.commit().
Changes:
- Save cart and shop UUIDs before commit to avoid detached access
- Re-query cart from database after commit using get_cart_by_id
- Pass shop_id parameter in cart URL to properly set request.shop
- Use saved UUID variables instead of accessing detached objects
This ensures request.shop_location is correctly resolved so the
handling options (local pickup, delivery, shipping) render properly
in the cart template.
All 357 tests now pass including the physical product handling test.
Added .col-status { display: none; } and mobile portrait media rules to hide columns 3–6, show the new Status column, and size Sellable/Price/Size for mobile.
Enabled title wrapping, ensured consistent header fonts, tuned column widths (Sellable wider, Status fixed, Size narrower), and added status-line spacing plus decimal-dot styling.
Combined visibility emoji plus bundle/physical/ready indicators into stacked status rows.
Wrapped price and size values so decimal points render through span.decimal-dot for bold dots.
This test closes the coverage gap that allowed the CSRF token bug to
reach production. The handling option form requires CSRF protection but
no test exercised this code path because all existing functional tests
use digital products.
New test verifies:
- Physical product handling option form renders correctly
- CSRF token is present and accepted by the view
- All handling options work (pickup, delivery, local/intl shipping)
- Form submission succeeds without 400 Bad CSRF Token error
Closes test coverage gap for make_post_sell/views/cart.py:404
The handling option form was missing the CSRF token include, causing
a "400 Bad CSRF Token" error when users tried to select shipping
options. Added the missing {% include "snippets/csrf.j2" %} to match
the pattern used by all other POST forms in the cart template.
Fixes cart.j2:144 - handling option form now includes CSRF protection
When coupons reduced cart total to $0, the checkout page displayed no
button to finalize the order. This occurred because the Stripe button
only appears with active_card, and crypto buttons only appear when
cart.requires_payment is True. Users without saved payment methods
were unable to complete free orders.
Added dedicated "Confirm Free Checkout" button that displays when
cart.requires_payment is False, allowing users to finalize free orders.
Enhanced test coverage to verify button appears in checkout page HTML.
Remove session-based status tracking that was storing crypto_payment_status_{payment_id}
for every payment, causing cookie size to exceed 4KB limit with 35+ payments.
- Remove: request.session[session_key] = current_status
- Remove: previous_status tracking via session
- Keep: Flash messages for status notifications (they don't bloat cookies)
Flash messages now trigger on every poll instead of just transitions, but this
prevents the cookie bloat issue. Client-side can handle transition detection if needed.
The removePaymentElements() and replaceButtonsWithInvoiceLink() JavaScript
functions were using incorrect CSS selectors that tried to match hardcoded
inline style attributes (e.g., div[style*="grid-template-columns: 1fr 1fr 1fr"])
which don't exist in the actual template HTML.
Updated both functions to use the correct CSS class selectors:
- '.payment-grid' for QR code and payment instructions
- '.payment-buttons' for copy buttons and wallet links
This ensures that when a payment transitions to 'received' status, the polling
mechanism immediately hides the payment UI elements without requiring a page
refresh, preventing potential duplicate payments.
Fixed selectors in crypto_checkout.j2:150-162 and crypto_checkout.j2:165-206