Jump to content

A lightweight, centralized marketplace with JReviews and Stripe Checkout


Miguel B.

Recommended Posts

I wanted to share a simple setup where I’m using JReviews listings as purchasable products with Stripe Checkout, without turning the site into a full e-commerce system.

This is not a full marketplace implementation, but a lightweight, centralized setup designed for a specific use case.

In short, this code does the following:

  1. Listings are created by users (each listing belongs to a seller)
  2. Sellers can optionally enable a “Shop now” button using a custom radio field
  3. The button only appears if: "a price is set" and "a supported shipping option is selected"
  4. Shipping is chosen from a predefined list (package size/weight), and the actual cost is calculated in PHP
  5. Clicking the button redirects the buyer to a custom Stripe Checkout session
  6. The platform receives the payment first and handles delivery confirmation and payouts later

This results in a lightweight, centralized marketplace flow: no carts, no WooCommerce, no forced checkout, just listings with an optional purchase path.

Below is a simplified example of the template logic used to display the button.

Step 1: Define price and shipping using custom fields

The first step was to define price and shipping logic using JReviews custom fields.

I created:

  1. A decimal custom field for the product price
  2. A single-select custom field for shipping, where the listing owner selects a predefined package type (size/weight)

The shipping example shown here works for my specific use case, where shipping is limited to a single country. Shipping costs are predefined and managed centrally, which keeps the setup simple.

Different setups may require different shipping logic depending on their needs.

Step 2: Conditional “Shop now” button in details.thtml

In this step, I add a conditional “Shop now" button to the listing detail page.

The "Shop now" button:

  1. Is optional and controlled by the listing owner using a radio custom field (“activate / don’t activate”).
  2. Only appears if a valid price and a supported shipping option are set
  3. Calculates the shipping cost internally based on a predefined option
  4. Redirects the buyer to a custom Stripe Checkout endpoint

This keeps checkout logic centralized and avoids exposing pricing rules to users.

<?php
$payment_option = $CustomFields->fieldValue('jr_opcionescobrar', $listing);
$precio_raw     = $CustomFields->fieldValue('jr_precio', $listing);
$opcion_envio   = $CustomFields->fieldValue('jr_gastosdeenvioespana', $listing);

$precio = (float) str_replace(',', '.', $precio_raw);

// Predefined shipping prices (mapped internally)
$precios_envio = [
    'envio-gratis'        => 0,
    'envio-ligero'        => 2.95,
    'paquete-pequeno'     => 4.95,
    'paquete-mediano'     => 6.95,
    'paquete-grande'      => 8.95,
    'voluminoso-especial' => null
];

// Resolve shipping cost
$envio = null;
if (isset($precios_envio[$opcion_envio])) {
    $envio = $precios_envio[$opcion_envio];
}

/* ----------- start internal CTA ------------ */

// Show button only if secure payment is enabled
// and the listing has valid price + supported shipping
if (
    $payment_option === 'pago-seguro'
    && $precio > 0
    && $envio !== null
) :

    $url_pago = home_url('/pago-seguro?') . http_build_query([
        'anuncio_id'  => (int) $listing['Listing']['listing_id'],
        'precio'      => $precio,
        'envio'       => $envio,
        'titulo'      => sanitize_text_field($listing['Listing']['title']),
        'artesano_id' => (int) $listing['Listing']['user_id']
    ]);
?>
<p>
    <a class="wp-block-button__link wp-element-button"
       href="<?php echo esc_url($url_pago); ?>"
       style="display:block;width:100%;text-align:center;"
       aria-label="Shop now">
        <strong>Shop now</strong>
    </a>
</p>
<?php endif; ?>

Step 3: Create a Stripe Checkout session

 

This endpoint is called when the buyer clicks the “Shop now” button.

Its responsibility is intentionally small and focused:

  1. Receive the listing data from the button (ID, price, shipping, seller)
  2. Build Stripe line items for product and shipping
  3. Create a Stripe Checkout Session
  4. Redirect the buyer to Stripe to complete the payment

The buyer only pays product + shipping.
No marketplace fees are shown during checkout.

<?php
/**
 * Technical endpoint to create a Stripe Checkout session
 * This is not a visual page and does not load headers or footers
 */

// Load Stripe
require_once get_template_directory() . '/stripe/init.php';

// Stripe API key (stored in wp-config.php)
\Stripe\Stripe::setApiKey(STRIPE_SECRET_KEY);

// ===============================
// Read parameters from the button
// ===============================
$anuncio_id  = isset($_GET['anuncio_id']) ? (int) $_GET['anuncio_id'] : 0;
$precio      = isset($_GET['precio']) ? (float) str_replace(',', '.', $_GET['precio']) : 0;
$envio       = isset($_GET['envio']) ? (float) str_replace(',', '.', $_GET['envio']) : 0;
$titulo      = isset($_GET['titulo']) ? sanitize_text_field($_GET['titulo']) : 'Product';
$artesano_id = isset($_GET['artesano_id']) ? (int) $_GET['artesano_id'] : 0;

// ===============================
// Basic validation
// ===============================
if ($anuncio_id <= 0 || $precio <= 0) {
    wp_die('Invalid payment parameters.');
}

// ===============================
// Build Stripe line items
// ===============================
$line_items = [];

// Product
$line_items[] = [
    'price_data' => [
        'currency' => 'eur',
        'product_data' => [
            'name' => $titulo,
        ],
        'unit_amount' => (int) round($precio * 100),
    ],
    'quantity' => 1,
];

// Shipping
if ($envio > 0) {
    $line_items[] = [
        'price_data' => [
            'currency' => 'eur',
            'product_data' => [
                'name' => 'Shipping',
            ],
            'unit_amount' => (int) round($envio * 100),
        ],
        'quantity' => 1,
    ];
}

// ===============================
// Create Stripe Checkout session
// ===============================
try {

    $session = \Stripe\Checkout\Session::create([
        'locale' => 'es',
        'mode' => 'payment',
        'payment_method_types' => ['card'],
        'line_items' => $line_items,

        'success_url' => home_url('/gracias-por-tu-compra/?session_id={CHECKOUT_SESSION_ID}'),
        'cancel_url'  => home_url('/pago-cancelado/'),

        'metadata' => [
            'anuncio_id'  => $anuncio_id,
            'artesano_id' => $artesano_id,
            'buyer_id'    => get_current_user_id(),
        ],

        'customer_creation' => 'always',

        'shipping_address_collection' => [
            'allowed_countries' => ['ES'],
        ],

        'phone_number_collection' => [
            'enabled' => true,
        ],
    ]);

    // Redirect to Stripe Checkout
    wp_redirect($session->url);
    exit;

} catch (\Exception $e) {
    wp_die('Error creating payment: ' . esc_html($e->getMessage()));
}

Final notes: 

The checkout flow also relies on standard WordPress pages for handling success and cancellation states (for example “Payment successful” and “Payment cancelled”), which are used as return URLs from Stripe Checkout.

The endpoint file lives in themes/your-theme/

The Stripe PHP library is included inside the theme (for example in themes/your-theme/stripe/)

The Stripe secret key is defined in wp-config.php

define('STRIPE_SECRET_KEY', 'your key here');
Edited by Miguel B.
  • Like 2
Link to comment
×
×
  • Create New...