Probo Configurator Price API
August 7, 2025 Β· AI Conversations
jQuery('#launchConfigurator').attr('disabled', true).css('opacity', 0.5);
function page() {
return {
updateModal() {
const result = window.proboConfigurator.getResult();
const code = window.proboConfigurator.getConfiguration();
console.log("π§Ύ Probo Configurator Result:", result);
console.log("π§Ύ Probo Configurator Configuration:", code);
}
}
}
// Listen for the Probo Configurator's "finished" event
window.addEventListener("proboConfigurator:finished", async function (event) {
console.log("β
Configuration finished");
jQuery('#launchConfigurator').attr('disabled', false).css('opacity', 1);
// Optionally, you can call updateModal() here if you want to log automatically
const result = window.proboConfigurator.getResult();
const code = window.proboConfigurator.getConfiguration();
console.log("π― Final Result:", result);
console.log("π¦ Final Configuration:", code);
});
how call price api from probo
### β Step-by-step Integration
Assuming the API endpoint is something like `https://api.proboprints.com/v2/price`, here's how you can do it using `fetch` or jQuery's `$.ajax`.
—
### β 1. Using `fetch` (Recommended Modern Way)
window.addEventListener("proboConfigurator:finished", async function (event) {
console.log("β
Configuration finished");
jQuery('#launchConfigurator').attr('disabled', false).css('opacity', 1);
const result = window.proboConfigurator.getResult();
const config = window.proboConfigurator.getConfiguration();
console.log("π― Final Result:", result);
console.log("π¦ Final Configuration:", config);
try {
const response = await fetch("https://api.proboprints.com/v2/price", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer YOUR_API_TOKEN" // replace with your token if needed
},
body: JSON.stringify({
configuration: config
})
});
const priceData = await response.json();
console.log("π° Price API Response:", priceData);
// You can now use `priceData` to update your UI, modal, etc.
} catch (error) {
console.error("β Price API Error:", error);
}
});
—
### β 2. Using jQuery AJAX (Alternative)
window.addEventListener("proboConfigurator:finished", function (event) {
console.log("β
Configuration finished");
jQuery('#launchConfigurator').attr('disabled', false).css('opacity', 1);
const result = window.proboConfigurator.getResult();
const config = window.proboConfigurator.getConfiguration();
console.log("π― Final Result:", result);
console.log("π¦ Final Configuration:", config);
$.ajax({
url: "https://api.proboprints.com/v2/price",
method: "POST",
contentType: "application/json",
headers: {
"Authorization": "Bearer YOUR_API_TOKEN" // if needed
},
data: JSON.stringify({
configuration: config
}),
success: function(priceData) {
console.log("π° Price API Response:", priceData);
// Update UI with priceData
},
error: function(err) {
console.error("β Price API Error:", err);
}
});
});
—
### π§ Notes:
– Replace `YOUR_API_TOKEN` with your actual Probo API key/token, if required.
– The payload (`configuration`) should match the expected format by Probo's pricing API.
– You can move the API call to a dedicated method inside your Alpine.js/Vue component if you want better structure.
Let me know if you want to also **display the price inside a modal or DOM element** after getting the response.
calculation_id
:
80760555
code
:
200
message
:
"Options calculated"
products
:
Array(1)
0
:
amount
:
2
available_options
:
[]
can_order
:
true
code
:
"banner"
height
:
12
id
:
1
length
:
0
linked_products
:
[{β¦}]
prices_per_product
:
24
:
{purchase_base_price: 3.865, purchase_rush_surcharge: 2, purchase_price: 5.865, purchase_price_incl_vat: 7.0967, sales_price: 0, β¦}
30
:
{purchase_base_price: 3.865, purchase_rush_surcharge: 2, purchase_price: 5.865, purchase_price_incl_vat: 7.0967, sales_price: 0, β¦}
48
:
{purchase_base_price: 3.865, purchase_rush_surcharge: 1.5, purchase_price: 5.365, purchase_price_incl_vat: 6.4917, sales_price: 0, β¦}
72
:
{purchase_base_price: 3.865, purchase_rush_surcharge: 0.75, purchase_price: 4.615, purchase_price_incl_vat: 5.5842, sales_price: 0, β¦}
96
:
{purchase_base_price: 3.865, purchase_rush_surcharge: 0, purchase_price: 3.865, purchase_price_incl_vat: 4.6767, sales_price: 0, β¦}
120
:
{purchase_base_price: 3.865, purchase_rush_surcharge: 0, purchase_price: 3.865, purchase_price_incl_vat: 4.6767, sales_price: 0, β¦}
144
:
{purchase_base_price: 3.865, purchase_rush_surcharge: 0, purchase_price: 3.865, purchase_price_incl_vat: 4.6767, sales_price: 0, β¦}
168
:
{purchase_base_price: 3.865, purchase_rush_surcharge: 0, purchase_price: 3.865, purchase_price_incl_vat: 4.6767, sales_price: 0, β¦}
192
:
{purchase_base_price: 3.865, purchase_rush_surcharge: 0, purchase_price: 3.865, purchase_price_incl_vat: 4.6767, sales_price: 0, β¦}
216
:
{purchase_base_price: 3.865, purchase_rush_surcharge: 0, purchase_price: 3.865, purchase_price_incl_vat: 4.6767, sales_price: 0, β¦}
240
:
{purchase_base_price: 3.865, purchase_rush_surcharge: 0, purchase_price: 3.865, purchase_price_incl_vat: 4.6767, sales_price: 0, β¦}
264
:
{purchase_base_price: 3.865, purchase_rush_surcharge: 0, purchase_price: 3.865, purchase_price_incl_vat: 4.6767, sales_price: 0, β¦}
288
:
{purchase_base_price: 3.865, purchase_rush_surcharge: 0, purchase_price: 3.865, purchase_price_incl_vat: 4.6767, sales_price: 0, β¦}
312
:
{purchase_base_price: 3.865, purchase_rush_surcharge: 0, purchase_price: 3.865, purchase_price_incl_vat: 4.6767, sales_price: 0, β¦}
336
:
{purchase_base_price: 3.865, purchase_rush_surcharge: 0, purchase_price: 3.865, purchase_price_incl_vat: 4.6767, sales_price: 0, β¦}
360
:
{purchase_base_price: 3.865, purchase_rush_surcharge: 0, purchase_price: 3.865, purchase_price_incl_vat: 4.6767, sales_price: 0, β¦}
384
:
{purchase_base_price: 3.865, purchase_rush_surcharge: 0, purchase_price: 3.865, purchase_price_incl_vat: 4.6767, sales_price: 0, β¦}
408
:
{purchase_base_price: 3.865, purchase_rush_surcharge: 0, purchase_price: 3.865, purchase_price_incl_vat: 4.6767, sales_price: 0, β¦}
432
:
{purchase_base_price: 3.865, purchase_rush_surcharge: 0, purchase_price: 3.865, purchase_price_incl_vat: 4.6767, sales_price: 0, β¦}
456
:
{purchase_base_price: 3.865, purchase_rush_surcharge: 0, purchase_price: 3.865, purchase_price_incl_vat: 4.6767, sales_price: 0, β¦}
480
:
{purchase_base_price: 3.865, purchase_rush_surcharge: 0, purchase_price: 3.865, purchase_price_incl_vat: 4.6767, sales_price: 0, β¦}
[[Prototype]]
:
Object
prices_total
:
24
:
{purchase_base_price: 7.73, purchase_rush_surcharge: 4, purchase_price: 11.73, purchase_price_incl_vat: 14.19, sales_price: 0, β¦}
30
:
{purchase_base_price: 7.73, purchase_rush_surcharge: 4, purchase_price: 11.73, purchase_price_incl_vat: 14.19, sales_price: 0, β¦}
48
:
{purchase_base_price: 7.73, purchase_rush_surcharge: 3, purchase_price: 10.73, purchase_price_incl_vat: 12.98, sales_price: 0, β¦}
72
:
{purchase_base_price: 7.73, purchase_rush_surcharge: 1.5, purchase_price: 9.23, purchase_price_incl_vat: 11.17, sales_price: 0, β¦}
96
:
{purchase_base_price: 7.73, purchase_rush_surcharge: 0, purchase_price: 7.73, purchase_price_incl_vat: 9.35, sales_price: 0, β¦}
120
:
{purchase_base_price: 7.73, purchase_rush_surcharge: 0, purchase_price: 7.73, purchase_price_incl_vat: 9.35, sales_price: 0, β¦}
144
:
{purchase_base_price: 7.73, purchase_rush_surcharge: 0, purchase_price: 7.73, purchase_price_incl_vat: 9.35, sales_price: 0, β¦}
168
:
{purchase_base_price: 7.73, purchase_rush_surcharge: 0, purchase_price: 7.73, purchase_price_incl_vat: 9.35, sales_price: 0, β¦}
192
:
{purchase_base_price: 7.73, purchase_rush_surcharge: 0, purchase_price: 7.73, purchase_price_incl_vat: 9.35, sales_price: 0, β¦}
216
:
{purchase_base_price: 7.73, purchase_rush_surcharge: 0, purchase_price: 7.73, purchase_price_incl_vat: 9.35, sales_price: 0, β¦}
240
:
{purchase_base_price: 7.73, purchase_rush_surcharge: 0, purchase_price: 7.73, purchase_price_incl_vat: 9.35, sales_price: 0, β¦}
264
:
{purchase_base_price: 7.73, purchase_rush_surcharge: 0, purchase_price: 7.73, purchase_price_incl_vat: 9.35, sales_price: 0, β¦}
288
:
{purchase_base_price: 7.73, purchase_rush_surcharge: 0, purchase_price: 7.73, purchase_price_incl_vat: 9.35, sales_price: 0, β¦}
312
:
{purchase_base_price: 7.73, purchase_rush_surcharge: 0, purchase_price: 7.73, purchase_price_incl_vat: 9.35, sales_price: 0, β¦}
336
:
{purchase_base_price: 7.73, purchase_rush_surcharge: 0, purchase_price: 7.73, purchase_price_incl_vat: 9.35, sales_price: 0, β¦}
360
:
{purchase_base_price: 7.73, purchase_rush_surcharge: 0, purchase_price: 7.73, purchase_price_incl_vat: 9.35, sales_price: 0, β¦}
384
:
{purchase_base_price: 7.73, purchase_rush_surcharge: 0, purchase_price: 7.73, purchase_price_incl_vat: 9.35, sales_price: 0, β¦}
408
:
{purchase_base_price: 7.73, purchase_rush_surcharge: 0, purchase_price: 7.73, purchase_price_incl_vat: 9.35, sales_price: 0, β¦}
432
:
{purchase_base_price: 7.73, purchase_rush_surcharge: 0, purchase_price: 7.73, purchase_price_incl_vat: 9.35, sales_price: 0, β¦}
456
:
{purchase_base_price: 7.73, purchase_rush_surcharge: 0, purchase_price: 7.73, purchase_price_incl_vat: 9.35, sales_price: 0, β¦}
480
:
{purchase_base_price: 7.73, purchase_rush_surcharge: 0, purchase_price: 7.73, purchase_price_incl_vat: 9.35, sales_price: 0, β¦}
[[Prototype]]
:
Object
selected_options
:
Array(7)
0
:
{type_code: 'width', name: 'Width', label: null, description: null, value: '11', β¦}
1
:
{type_code: 'height', name: 'Height', label: null, description: null, value: '12', β¦}
2
:
{type_code: 'amount', name: 'Amount', label: null, description: null, value: '2', β¦}
3
:
{type_code: 'radio', name: 'Single-sided', label: null, description: null, value: null, β¦}
4
:
{type_code: 'radio', name: 'Banner 510', label: 'Most popular', description: 'PVC banner suited for both indoor and outdoor applications.', value: null, β¦}
5
:
{type_code: 'radio', name: 'Use same finish for all edges', label: null, description: 'All edges of the banner receive the same finish.', value: null, β¦}
6
:
{type_code: 'radio', name: 'Cut', label: null, description: 'The material is cut to the desired size.', value: null, β¦}
length
:
7
[[Prototype]]
:
Array(0)
we above out after call configurator how to retieve price from it
—
### β From the Data You Shared:
You can access:
– `response.products[0].prices_total[delivery_time].purchase_price_incl_vat` β total price **(incl. VAT)** for given delivery time
– `response.products[0].prices_per_product[delivery_time].purchase_price_incl_vat` β **per unit price** (incl. VAT)
—
### π§ Example to Extract Final Price (Assuming 96-hour delivery):
window.addEventListener("proboConfigurator:finished", async function () {
jQuery('#launchConfigurator').attr('disabled', false).css('opacity', 1);
const config = window.proboConfigurator.getConfiguration();
try {
const res = await fetch("https://api.proboprints.com/v2/price", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer YOUR_API_TOKEN"
},
body: JSON.stringify({ configuration: config })
});
const data = await res.json();
console.log("π° Price Response", data);
// Access price data
const product = data.products[0];
const deliveryTime = 96; // in hours (can be 24, 48, 72, 96, etc.)
const totalPrice = product.prices_total[deliveryTime].purchase_price_incl_vat;
const perUnitPrice = product.prices_per_product[deliveryTime].purchase_price_incl_vat;
const amount = product.amount;
console.log(`π§Ύ Total Price (${amount}x): β¬${totalPrice.toFixed(2)}`);
console.log(`π΅ Price per Unit: β¬${perUnitPrice.toFixed(2)}`);
// Inject into HTML if needed
document.querySelector('#totalPrice').innerText = `β¬${totalPrice.toFixed(2)}`;
document.querySelector('#perUnitPrice').innerText = `β¬${perUnitPrice.toFixed(2)}`;
} catch (err) {
console.error("β Error fetching price:", err);
}
});
—
### β HTML Example:
<div>
<p>Price per Unit: <span id="perUnitPrice">β</span></p>
<p>Total Price: <span id="totalPrice">β</span></p>
</div>
—
### π Optional: Auto-pick Cheapest Delivery Time
To automatically pick the delivery time with the **lowest total price**, you can do this:
let bestTime = null;
let lowest = Infinity;
for (const [time, price] of Object.entries(product.prices_total)) {
if (price.purchase_price_incl_vat < lowest) {
lowest = price.purchase_price_incl_vat;
bestTime = time;
}
}
console.log(`π Best delivery time: ${bestTime}h, Price: β¬${lowest.toFixed(2)}`);
—
Would you like to format and show delivery options as a dropdown or select best price automatically?
const payload = window.proboConfigurator.getRaw();
now display all prices includeing radio
and let user click on any radio want to add to cart in woocommerce
—
### β 1. **Extract & Display All Delivery Prices as Radio Buttons**
Each delivery time (e.g., 24h, 48h, 72h, 96hβ¦) is a **key** inside `prices_total`.
We'll generate a set of “ buttons for each delivery option.
—
### β 2. **Let User Click to Select a Delivery Option & Add to Cart**
When the user clicks a radio, you can:
– Save the selected delivery time
– Enable a βAdd to Cartβ button
– On click of "Add to Cart", use AJAX or redirect to send data to WooCommerce cart (via a custom endpoint or product add logic)
—
### β Full Working Example:
<div id="probo-price-options">
<h3>Select Delivery Option</h3>
<div id="deliveryOptions"></div>
<button id="addToCartBtn" disabled>Add to Cart</button>
</div>
window.addEventListener("proboConfigurator:finished", function () {
const payload = window.proboConfigurator.getRaw();
const product = payload.products[0];
const prices = product.prices_total;
const amount = product.amount;
const deliveryOptionsContainer = document.getElementById("deliveryOptions");
deliveryOptionsContainer.innerHTML = "";
// Generate radio buttons for each delivery time
Object.entries(prices).forEach(([hours, price]) => {
const label = document.createElement("label");
label.innerHTML = `
<input type="radio" name="delivery_time" value="${hours}">
${hours}h β β¬${price.purchase_price_incl_vat.toFixed(2)} (for ${amount} units)
`;
deliveryOptionsContainer.appendChild(label);
deliveryOptionsContainer.appendChild(document.createElement("br"));
});
// Listen for selection
let selectedHours = null;
deliveryOptionsContainer.addEventListener("change", (e) => {
selectedHours = e.target.value;
document.getElementById("addToCartBtn").disabled = false;
});
// Add to cart button logic
document.getElementById("addToCartBtn").addEventListener("click", () => {
if (!selectedHours) return alert("Please select a delivery time!");
// Gather all needed data
const config = window.proboConfigurator.getConfiguration(); // for saving config
const selectedPrice = prices[selectedHours];
const selectedOptions = product.selected_options;
// Send to WooCommerce cart (Example: POST to custom PHP endpoint or redirect)
console.log("π Add to cart with config:", config);
console.log("π Selected delivery time:", selectedHours);
console.log("πΆ Price to add:", selectedPrice);
console.log("π¦ Options:", selectedOptions);
// π Here you can send via AJAX to your custom endpoint to add to WooCommerce cart
// OR redirect with query params if you're not using AJAX.
// Example (redirect, custom implementation needed in backend):
const productId = 1234; // your WooCommerce product ID
const url = `/add-to-cart-handler/?product_id=${productId}&price=${selectedPrice.purchase_price_incl_vat}&delivery=${selectedHours}&config=${encodeURIComponent(JSON.stringify(config))}`;
window.location.href = url;
});
});
—
### β Backend WooCommerce Integration
You need a custom handler on `/add-to-cart-handler/` that:
1. Creates a WooCommerce cart item for the given product ID
2. Adds metadata like:
– Config JSON
– Delivery time
– Price override
3. Redirects to cart or checkout
If needed, I can help you write this WordPress PHP handler too (`add_to_cart_handler.php` or via a `add_action('template_redirect')`).
—
Would you like the full PHP code for the WooCommerce cart handler that handles this JS request?
—
### β Updated JavaScript to Display Only Extra Price (Rush) Options
window.addEventListener("proboConfigurator:finished", function () {
const payload = window.proboConfigurator.getRaw();
const product = payload.products[0];
const prices = product.prices_total;
const amount = product.amount;
const deliveryOptionsContainer = document.getElementById("deliveryOptions");
deliveryOptionsContainer.innerHTML = "";
// Filter and display only those with rush surcharge > 0
const filtered = Object.entries(prices).filter(
([, price]) => price.purchase_rush_surcharge > 0
);
if (filtered.length === 0) {
deliveryOptionsContainer.innerHTML = "<p>No rush delivery options with extra charges found.</p>";
return;
}
// Create radio buttons
filtered.forEach(([hours, price]) => {
const label = document.createElement("label");
label.innerHTML = `
<input type="radio" name="delivery_time" value="${hours}">
${hours}h β β¬${price.purchase_price_incl_vat.toFixed(2)} (Rush Fee: β¬${price.purchase_rush_surcharge.toFixed(2)})
`;
deliveryOptionsContainer.appendChild(label);
deliveryOptionsContainer.appendChild(document.createElement("br"));
});
// Listen for user selection
let selectedHours = null;
deliveryOptionsContainer.addEventListener("change", (e) => {
selectedHours = e.target.value;
document.getElementById("addToCartBtn").disabled = false;
});
// Handle Add to Cart
document.getElementById("addToCartBtn").addEventListener("click", () => {
if (!selectedHours) return alert("Please select a delivery option!");
const selectedPrice = prices[selectedHours];
const config = window.proboConfigurator.getConfiguration();
console.log("π Add to Cart:");
console.log("Delivery Time:", selectedHours);
console.log("Rush Fee:", selectedPrice.purchase_rush_surcharge);
console.log("Price incl. VAT:", selectedPrice.purchase_price_incl_vat);
console.log("Config:", config);
// TODO: AJAX or redirect to WooCommerce Add-to-Cart with this data
});
});
—
### β HTML:
<div id="probo-price-options">
<h3>Select Rush Delivery Option (Extra Charge)</h3>
<div id="deliveryOptions"></div>
<button id="addToCartBtn" disabled>Add to Cart</button>
</div>
—
### β Example Output:
[o] 24h β β¬14.19 (Rush Fee: β¬4.00)
[o] 48h β β¬12.98 (Rush Fee: β¬3.00)
[o] 72h β β¬11.17 (Rush Fee: β¬1.50)
—
Let me know if you want to also show **regular free options (surcharge = 0)** in a **separate list** for comparison β or handle differently in WooCommerce cart.
1. β
**Rush Delivery Options** β where `purchase_rush_surcharge > 0`
2. π **No Rush Options** β where `purchase_rush_surcharge === 0`
This gives the user a clear choice between **paying more for faster delivery** vs **waiting longer for cheaper price**.
—
### β Full Code to Display Both Sets
<div id="probo-price-options">
<h3>Rush Delivery Options (Extra Fee)</h3>
<div id="rushOptions"></div>
<h3 style="margin-top:20px;">No Rush (Standard Delivery)</h3>
<div id="noRushOptions"></div>
<button id="addToCartBtn" disabled>Add to Cart</button>
</div>
“`js
window.addEventListener("proboConfigurator:finished", function () {
const payload = window.proboConfigurator.getRaw();
const product = payload.products[0];
const prices = product.prices_total;
const amount = product.amount;
const rushContainer = document.getElementById("rushOptions");
const noRushContainer = document.getElementById("noRushOptions");
const addToCartBtn = document.getElementById("addToCartBtn");
rushContainer.innerHTML = "";
noRushContainer.innerHTML = "";
addToCartBtn.disabled = true;
let selectedHours = null;
—
### β Updated HTML
<div id="probo-price-options">
<h3>Select Delivery Option</h3>
<div id="deliveryOptions"></div>
<button id="addToCartBtn" disabled>Add to Cart</button>
</div>
—
### β JavaScript: Display Rush & No-Rush Together (Marked Clearly)
window.addEventListener("proboConfigurator:finished", function () {
const payload = window.proboConfigurator.getRaw();
const product = payload.products[0];
const prices = product.prices_total;
const amount = product.amount;
const deliveryOptionsContainer = document.getElementById("deliveryOptions");
const addToCartBtn = document.getElementById("addToCartBtn");
deliveryOptionsContainer.innerHTML = "";
addToCartBtn.disabled = true;
let selectedHours = null;
Object.entries(prices).forEach(([hours, price]) => {
const isRush = price.purchase_rush_surcharge > 0;
const label = document.createElement("label");
label.style.display = "block";
label.style.marginBottom = "8px";
label.innerHTML = `
<input type="radio" name="delivery_time" value="${hours}">
${hours}h β β¬${price.purchase_price_incl_vat.toFixed(2)}
${isRush ? `<span style="color: red;">(+ Rush Fee: β¬${price.purchase_rush_surcharge.toFixed(2)})</span>` : `<span style="color: green;">(No Extra Fee)</span>`}
`;
deliveryOptionsContainer.appendChild(label);
});
// Handle radio change
deliveryOptionsContainer.addEventListener("change", (e) => {
selectedHours = e.target.value;
addToCartBtn.disabled = false;
});
// Handle Add to Cart
addToCartBtn.addEventListener("click", () => {
if (!selectedHours) return alert("Please select a delivery option");
const selectedPrice = prices[selectedHours];
const config = window.proboConfigurator.getConfiguration();
console.log("π Adding to cart with:");
console.log("Delivery Time:", selectedHours);
console.log("Rush Fee:", selectedPrice.purchase_rush_surcharge);
console.log("Total Price (incl. VAT):", selectedPrice.purchase_price_incl_vat);
console.log("Config:", config);
// TODO: Send this data to WooCommerce or backend
});
});
—
### β Example Output in UI
(o) 24h β β¬14.19 (+ Rush Fee: β¬4.00)
(o) 48h β β¬12.98 (+ Rush Fee: β¬3.00)
(o) 72h β β¬11.17 (+ Rush Fee: β¬1.50)
(o) 96h β β¬9.35 (No Extra Fee)
(o) 120h β β¬9.35 (No Extra Fee)
—
Let me know if you want:
– To **highlight the cheapest option**
– To show **estimated delivery date**
– Or handle **Add to Cart logic** with WooCommerce via AJAX or URL redirect.
—
### β
Logic:
– Loop through delivery options sorted by hours.
– Find the **first** one where `purchase_rush_surcharge === 0` β mark that as **"No Rush"**.
– All earlier ones with surcharge are just regular rush options.
—
### β Final Code (All in Same Section, Only One "No Rush")
window.addEventListener("proboConfigurator:finished", function () {
const payload = window.proboConfigurator.getRaw();
const product = payload.products[0];
const prices = product.prices_total;
const amount = product.amount;
const deliveryOptionsContainer = document.getElementById("deliveryOptions");
const addToCartBtn = document.getElementById("addToCartBtn");
deliveryOptionsContainer.innerHTML = "";
addToCartBtn.disabled = true;
let selectedHours = null;
// Sort delivery hours numerically
const sortedPrices = Object.entries(prices).sort((a, b) => Number(a[0]) - Number(b[0]));
let noRushMarked = false;
sortedPrices.forEach(([hours, price]) => {
const isNoRush = price.purchase_rush_surcharge === 0 && !noRushMarked;
const label = document.createElement("label");
label.style.display = "block";
label.style.marginBottom = "8px";
label.innerHTML = `
<input type="radio" name="delivery_time" value="${hours}">
${hours}h β β¬${price.purchase_price_incl_vat.toFixed(2)}
${isNoRush
? `<span style="color: green;">(No Rush)</span>`
: price.purchase_rush_surcharge > 0
? `<span style="color: red;">(+ Rush Fee: β¬${price.purchase_rush_surcharge.toFixed(2)})</span>`
: ""}
`;
deliveryOptionsContainer.appendChild(label);
if (isNoRush) noRushMarked = true;
});
// Handle selection
deliveryOptionsContainer.addEventListener("change", (e) => {
selectedHours = e.target.value;
addToCartBtn.disabled = false;
});
// Add to cart logic
addToCartBtn.addEventListener("click", () => {
if (!selectedHours) return alert("Please select a delivery option");
const selectedPrice = prices[selectedHours];
const config = window.proboConfigurator.getConfiguration();
console.log("π Adding to cart with:");
console.log("Delivery Time:", selectedHours);
console.log("Rush Fee:", selectedPrice.purchase_rush_surcharge);
console.log("Price incl. VAT:", selectedPrice.purchase_price_incl_vat);
console.log("Config:", config);
// TODO: integrate into WooCommerce add-to-cart
});
});
—
### β Example Output:
(o) 24h β β¬14.19 (+ Rush Fee: β¬4.00)
(o) 48h β β¬12.98 (+ Rush Fee: β¬3.00)
(o) 72h β β¬11.17 (+ Rush Fee: β¬1.50)
(o) 96h β β¬9.35 (No Rush)
(o) 120h β β¬9.35
(o) 144h β β¬9.35
Only the **first no-surcharge option (96h)** is labeled βNo Rushβ.
—
Would you like to style the No Rush option to **auto-select by default** or **highlight it visually** as the best value?
selected_options,calcution id and upload and uploaders to cart item meta Like add
common meta field probo_configure_meta and save in this when comletedd order after checkout
β On "Add to Cart" click:
1. Attach:
– `selected_options`
– `calculation_id`
– `upload` & `uploaders`
– `selected price (based on delivery time)`
2. Bundle these into a **single cart item meta field** called `probo_configure_meta`.
β Then:
3. Save `probo_configure_meta` to the WooCommerce **order item meta** after checkout so it stays with the order.
—
### β Frontend (JavaScript): Send to Add-to-Cart URL
Letβs assume:
– Your WooCommerce product has ID `1234`
– You pass `probo_configure_meta` via query param or via AJAX (recommended)
document.getElementById("addToCartBtn").addEventListener("click", () => {
if (!selectedHours) return alert("Please select a delivery option");
const payload = window.proboConfigurator.getRaw();
const selectedPrice = payload.products[0].prices_total[selectedHours];
const meta = {
selected_options: payload.products[0].selected_options,
calculation_id: payload.calculation_id,
upload: payload.upload,
uploaders: payload.uploaders,
selected_delivery: selectedHours,
price: selectedPrice,
};
const productId = 1234;
// Convert meta to base64 string to avoid URL issues
const metaEncoded = btoa(JSON.stringify(meta));
// Send to cart via GET
const url = `/add-to-cart-handler/?add-to-cart=${productId}&probo_configure_meta=${metaEncoded}`;
window.location.href = url;
});
—
### β Backend: Handle `probo_configure_meta` in Cart and Order
#### 1. **Add to Cart Item Meta**
add_filter('woocommerce_add_cart_item_data', function ($cart_item_data, $product_id) {
if (isset($_GET['probo_configure_meta'])) {
$decoded = json_decode(base64_decode(sanitize_text_field($_GET['probo_configure_meta'])), true);
$cart_item_data['probo_configure_meta'] = $decoded;
}
return $cart_item_data;
}, 10, 2);
—
#### 2. **Display in Cart (Optional)**
add_filter('woocommerce_get_item_data', function ($item_data, $cart_item) {
if (!empty($cart_item['probo_configure_meta'])) {
$meta = $cart_item['probo_configure_meta'];
$item_data[] = [
'name' => 'Delivery Time',
'value' => $meta['selected_delivery'] . 'h'
];
$item_data[] = [
'name' => 'Probo Config ID',
'value' => $meta['calculation_id']
];
}
return $item_data;
}, 10, 2);
—
#### 3. **Save to Order Item Meta**
add_action('woocommerce_add_order_item_meta', function ($item_id, $values, $cart_item_key) {
if (!empty($values['probo_configure_meta'])) {
wc_add_order_item_meta($item_id, 'probo_configure_meta', $values['probo_configure_meta']);
}
}, 10, 3);
—
### β Result:
– Cart stores everything under one field: `probo_configure_meta`
– Saved in database with order
– You can access it in backend like:
$meta = get_post_meta($order_item_id, 'probo_configure_meta', true);
—
Would you like this meta also shown in:
– Order confirmation emails?
– Admin order view?
– My Account β Orders?
Let me know and Iβll help you extend it.
—
### β 1. π **Frontend URL: Use WooCommerce Add-to-Cart + Custom Meta**
document.getElementById("addToCartBtn").addEventListener("click", () => {
if (!selectedHours) return alert("Please select a delivery option");
const payload = window.proboConfigurator.getRaw();
const selectedPrice = payload.products[0].prices_total[selectedHours];
const meta = {
selected_options: payload.products[0].selected_options,
calculation_id: payload.calculation_id,
upload: payload.upload,
uploaders: payload.uploaders,
selected_delivery: selectedHours,
price: selectedPrice,
};
const metaEncoded = btoa(JSON.stringify(meta)); // safely encode
const productId = 1234; // your WooCommerce product ID
const url = `/cart/?add-to-cart=${productId}&probo_configure_meta=${metaEncoded}`;
window.location.href = url;
});
—
### β 2. π **Capture Meta in `woocommerce_add_cart_item_data`**
add_filter('woocommerce_add_cart_item_data', function ($cart_item_data, $product_id) {
if (isset($_GET['probo_configure_meta'])) {
$meta = json_decode(base64_decode(sanitize_text_field($_GET['probo_configure_meta'])), true);
if ($meta && is_array($meta)) {
$cart_item_data['probo_configure_meta'] = $meta;
}
}
return $cart_item_data;
}, 10, 2);
—
### β 3. π¦ **Show Meta in Cart (optional)**
add_filter('woocommerce_get_item_data', function ($item_data, $cart_item) {
if (!empty($cart_item['probo_configure_meta'])) {
$meta = $cart_item['probo_configure_meta'];
$item_data[] = [
'name' => 'Probo Config ID',
'value' => $meta['calculation_id'] ?? 'β'
];
$item_data[] = [
'name' => 'Delivery Time',
'value' => ($meta['selected_delivery'] ?? '?') . 'h'
];
}
return $item_data;
}, 10, 2);
—
### β 4. β **Store Meta in Order Items**
add_action('woocommerce_add_order_item_meta', function ($item_id, $values, $cart_item_key) {
if (!empty($values['probo_configure_meta'])) {
wc_add_order_item_meta($item_id, 'probo_configure_meta', $values['probo_configure_meta']);
}
}, 10, 3);
—
### β 5. π **Read in Admin / Custom Email / Dashboard**
// Get in order loop
$meta = wc_get_order_item_meta($item_id, 'probo_configure_meta', true);
—
### β Result:
– β
User uses default WooCommerce `?add-to-cart=1234` flow.
– β
`probo_configure_meta` is captured, saved to cart, and stored in the order.
– β
No AJAX or REST API needed.
—
Would you like help **showing this config meta in the WooCommerce admin order page** or **showing in customer emails** too?
probo_configurator
so its shows when click add to cart
Sorry, this product cannot be purchased.
> **"Sorry, this product cannot be purchased."**
This happens because WooCommerce by default doesnβt allow custom product types to be **purchasable** unless explicitly marked so.
—
### β Step-by-step Fix for βThis product cannot be purchasedβ for custom product types
—
### β 1. Mark `probo_configurator` as **purchasable**
Add this to your `functions.php` or plugin:
add_filter('woocommerce_is_purchasable', function($purchasable, $product) {
if ($product->get_type() === 'probo_configurator') {
return true;
}
return $purchasable;
}, 10, 2);
—
### β 2. Enable Add to Cart for Custom Type
WooCommerce also restricts `add-to-cart` functionality for custom types unless you tell it how to handle them.
Tell WooCommerce that your custom type is "simple" in behavior:
add_filter('woocommerce_product_type_selector', function($types) {
$types['probo_configurator'] = __('Probo Configurator', 'your-textdomain');
return $types;
});
// Set your custom product type to behave like a simple product
add_action('init', function () {
class WC_Product_Probo_Configurator extends WC_Product_Simple {
public function get_type() {
return 'probo_configurator';
}
}
});
—
### β 3. Make Sure Itβs Set as Sold Individually (if needed)
You may also need to set some product properties in the admin:
– β
Make sure β**Sold Individually**β is **unchecked** unless you want 1 per order.
– β
Inventory: If managing stock, ensure the stock status is **In Stock**.
– β
Price: Even if you're replacing the price dynamically, add a dummy price like `1.00` in the product editor (to pass WooCommerce checks).
—
### β 4. If Price is Missing, WooCommerce Will Block Add to Cart
If you're not using a fixed price, force the product to be purchasable without a price:
add_filter('woocommerce_is_sold_individually', function($sold_individually, $product) {
if ($product->get_type() === 'probo_configurator') {
return false;
}
return $sold_individually;
}, 10, 2);
add_filter('woocommerce_product_get_price', function($price, $product) {
if ($product->get_type() === 'probo_configurator') {
return 1.00; // minimum dummy price
}
return $price;
}, 10, 2);
—
### β 5. Optional: Hide Add to Cart from Shop Loop (but allow direct add)
add_filter('woocommerce_is_visible', function($visible, $product_id) {
$product = wc_get_product($product_id);
if ($product->get_type() === 'probo_configurator') {
return false; // hides from shop page
}
return $visible;
}, 10, 2);
—
### β Final Result:
– β
`probo_configurator` products are **purchasable**
– β
Can be added to cart via default `?add-to-cart=ID` flow
– β
Your custom meta (`probo_configure_meta`) is saved as cart item data and order item meta
—
Would you like:
– To **hide price** on product page but show total after configurator?
– To customize the **single product page template** for this custom product type?
β Dynamically **set the product price in cart and checkout** based on the **selected delivery option price** (from the configurator radio).
You're already storing this in your `probo_configure_meta` under the `price` key, like:
price: {
purchase_price: 5.865,
purchase_price_incl_vat: 7.0967,
...
}
—
### β 1. Store `probo_configure_meta` in cart (you're already doing this):
You're passing it like this:
add_filter('woocommerce_add_cart_item_data', function ($cart_item_data, $product_id) {
if (isset($_GET['probo_configure_meta'])) {
$meta = json_decode(base64_decode(sanitize_text_field($_GET['probo_configure_meta'])), true);
if ($meta && is_array($meta)) {
$cart_item_data['probo_configure_meta'] = $meta;
}
}
return $cart_item_data;
}, 10, 2);
—
### β 2. Override Price in Cart Using Meta (Dynamic Price Per Cart Item)
Add this hook to modify the price dynamically from your meta:
add_filter('woocommerce_before_calculate_totals', function($cart) {
if (is_admin() && !defined('DOING_AJAX')) return;
foreach ($cart->get_cart() as $cart_item) {
if (
isset($cart_item['probo_configure_meta']['price']['purchase_price_incl_vat'])
&& is_numeric($cart_item['probo_configure_meta']['price']['purchase_price_incl_vat'])
) {
$custom_price = (float) $cart_item['probo_configure_meta']['price']['purchase_price_incl_vat'];
$cart_item['data']->set_price($custom_price);
}
}
}, 20, 1);
β
This will:
– Set the **cart item price** based on `purchase_price_incl_vat`
– Reflect in **cart total**, **checkout**, **emails**, and **orders**
—
### β Optional: Show it in Cart Line Item
(Already shown from earlier step)
add_filter('woocommerce_get_item_data', function ($item_data, $cart_item) {
if (!empty($cart_item['probo_configure_meta'])) {
$meta = $cart_item['probo_configure_meta'];
$item_data[] = [
'name' => 'Delivery Time',
'value' => ($meta['selected_delivery'] ?? '?') . 'h'
];
$item_data[] = [
'name' => 'Probo Price',
'value' => 'β¬' . number_format($meta['price']['purchase_price_incl_vat'], 2)
];
}
return $item_data;
}, 10, 2);
—
### β Final Result:
– πΈ Customer sees correct delivery-based price
– π§Ύ Price reflects in cart/checkout/order
– π¦ You have all Probo meta stored in order item
—
Let me know if you'd like to:
– Use `purchase_price` instead (excl. VAT)?
– Add a fallback price if missing?
– Format currency symbol dynamically?
I'm happy to help further.
—
### β
Goal:
– Use default `?add-to-cart=PRODUCT_ID` URL
– Set `quantity = amount` from configurator payload
– Still pass `probo_configure_meta` as usual
—
### β 1. π Frontend: Append `quantity` to URL based on `amount`
Update your existing redirect like this:
document.getElementById("addToCartBtn").addEventListener("click", () => {
if (!selectedHours) return alert("Please select a delivery option");
const payload = window.proboConfigurator.getRaw();
const selectedPrice = payload.products[0].prices_total[selectedHours];
const amount = payload.products[0].amount || 1;
const meta = {
selected_options: payload.products[0].selected_options,
calculation_id: payload.calculation_id,
upload: payload.upload,
uploaders: payload.uploaders,
selected_delivery: selectedHours,
price: selectedPrice
};
const metaEncoded = btoa(JSON.stringify(meta));
const productId = 1234;
const url = `/cart/?add-to-cart=${productId}&quantity=${amount}&probo_configure_meta=${metaEncoded}`;
window.location.href = url;
});
—
### β 2. β WooCommerce: Accept `quantity` Normally
You **donβt need to write extra PHP code** to handle `quantity` if it's passed via `?quantity=N` β WooCommerce handles it natively.
But just in case you need to force it from the `amount` inside meta (for example, if using AJAX or other workflows), you could add:
add_filter('woocommerce_add_cart_item_data', function ($cart_item_data, $product_id) {
if (isset($_GET['probo_configure_meta'])) {
$meta = json_decode(base64_decode(sanitize_text_field($_GET['probo_configure_meta'])), true);
if ($meta && is_array($meta)) {
$cart_item_data['probo_configure_meta'] = $meta;
// Optional fallback: force quantity from meta
if (!empty($meta['amount'])) {
$cart_item_data['probo_quantity'] = (int) $meta['amount'];
}
}
}
return $cart_item_data;
}, 10, 2);
Then you could hook `woocommerce_add_to_cart_quantity` to override quantity β but **passing it via the URL is cleaner and native**.
—
### β Summary
| What you want | Solution |
|——————————-|————————————|
| Use `amount` from configurator | Set `quantity=${amount}` in URL |
| Preserve custom meta | Pass via `probo_configure_meta` |
| Let WooCommerce calculate total price = price Γ quantity | β
Already handled via `set_price()` and `quantity` |
—
Let me know if:
– You want to **override quantity validation** (e.g. max limits)
– You want to **disable editing quantity in cart** after this
– Or auto-hide the product from shop pages (since itβs added via config)
—
### π§Ύ Desired Cart Breakdown:
1. **Unit Price** = `purchase_base_price`
2. **Quantity** = `amount` (from configurator)
3. **Line Subtotal (unit price Γ quantity)** = shown as base product cost
4. **Surcharge (rush fee)** = shown as **separate cart fee**, not included in base subtotal
—
### β Overview of Implementation:
| Data | Source | Handled In |
|————————–|—————————–|———————————-|
| Quantity (`amount`) | Probo Configurator payload | Set in `?quantity=` param |
| Unit price | `purchase_base_price` | Override in `set_price()` hook |
| Rush fee | `purchase_rush_surcharge` | Add via `woocommerce_cart_calculate_fees` |
| Custom meta | Whole config | Stored in `probo_configure_meta`|
—
### β Step-by-Step
—
### 1. β Update `set_price()` logic to use only `purchase_base_price`
add_action('woocommerce_before_calculate_totals', function ($cart) {
if (is_admin() && !defined('DOING_AJAX')) return;
foreach ($cart->get_cart() as $cart_item) {
if (
isset($cart_item['probo_configure_meta']['price']['purchase_base_price'])
&& is_numeric($cart_item['probo_configure_meta']['price']['purchase_base_price'])
) {
$unit_price = (float) $cart_item['probo_configure_meta']['price']['purchase_base_price'];
$cart_item['data']->set_price($unit_price);
}
}
}, 20, 1);
This sets **unit price** to the **base product price only**, not including surcharge.
—
### 2. β Add Rush Fee as Cart-Level Fee
add_action('woocommerce_cart_calculate_fees', function($cart) {
if (is_admin() && !defined('DOING_AJAX')) return;
foreach ($cart->get_cart() as $item) {
$meta = $item['probo_configure_meta'] ?? null;
if ($meta && isset($meta['price']['purchase_rush_surcharge'])) {
$surcharge = (float) $meta['price']['purchase_rush_surcharge'];
$qty = isset($item['quantity']) ? (int) $item['quantity'] : 1;
$total_surcharge = $surcharge * $qty;
if ($total_surcharge > 0) {
$label = 'Rush Fee for ' . ($meta['selected_delivery'] ?? 'selected') . 'h';
$cart->add_fee($label, $total_surcharge, true); // true = taxable
}
}
}
});
This adds the **rush surcharge** as a clearly labeled **fee**, and multiplies it by quantity.
—
### 3. β Optional: Show this nicely in Cart & Checkout
You're already showing metadata. For even clearer output:
add_filter('woocommerce_get_item_data', function ($item_data, $cart_item) {
if (!empty($cart_item['probo_configure_meta'])) {
$meta = $cart_item['probo_configure_meta'];
$item_data[] = [
'name' => 'Quantity from Probo',
'value' => $cart_item['quantity']
];
$item_data[] = [
'name' => 'Unit Price',
'value' => 'β¬' . number_format($meta['price']['purchase_base_price'], 2)
];
$item_data[] = [
'name' => 'Rush Surcharge (per unit)',
'value' => 'β¬' . number_format($meta['price']['purchase_rush_surcharge'], 2)
];
$item_data[] = [
'name' => 'Delivery Time',
'value' => ($meta['selected_delivery'] ?? '?') . 'h'
];
}
return $item_data;
}, 10, 2);
—
### β Final Result:
| Component | Value |
|—————|——————————————|
| Unit Price | From `purchase_base_price` |
| Qty | From `amount` in Probo Configurator |
| Subtotal | Unit Price Γ Qty |
| Rush Fee | Surcharge Γ Qty (as WooCommerce Fee) |
| Order Total | Subtotal + Surcharge |
—
Would you like:
– To **apply VAT separately** on rush fee?
– To **mark rush fee as non-taxable**?
– Or include rush fee as line item instead of cart fee?
Let me know how you'd like it structured.
You're already storing this in your `probo_configure_meta['selected_delivery']` field.
—
### β Here's how to save the delivery hours cleanly into the order:
#### π Hook into `woocommerce_add_order_item_meta`
Youβre already saving the full `probo_configure_meta` β now add a separate meta key like `probo_delivery_hours` for clarity:
add_action('woocommerce_add_order_item_meta', function ($item_id, $values, $cart_item_key) {
if (!empty($values['probo_configure_meta'])) {
$meta = $values['probo_configure_meta'];
// Save full config
wc_add_order_item_meta($item_id, 'probo_configure_meta', $meta);
// Save delivery hours (like "24h")
if (!empty($meta['selected_delivery'])) {
wc_add_order_item_meta($item_id, 'probo_delivery_hours', $meta['selected_delivery'] . 'h');
}
}
}, 10, 3);
—
### β Result in WooCommerce:
| Meta Key | Value |
|———————–|———|
| `probo_configure_meta` | full config array (JSON-encoded) |
| `probo_delivery_hours`| `24h`, `48h`, `96h`, etc. |
—
### π¦ Optional: Show on Order Admin Page
To display the delivery time in the **WooCommerce admin order page**, you can do this:
add_action('woocommerce_after_order_itemmeta', function($item_id, $item, $product){
$delivery = wc_get_order_item_meta($item_id, 'probo_delivery_hours');
if ($delivery) {
echo '<p><strong>Probo Delivery:</strong> ' . esc_html($delivery) . '</p>';
}
}, 10, 3);
—
Let me know if youβd also like to:
– Show it on **customer email**
– Show it in **My Account β Orders**
– Or display it as a **tooltip or tag** in the admin interface for fast scanning.
—
### β Summary Steps to Save Custom Cart Meta
| Step | What You Do | WooCommerce Hook |
|——|————————————-|——————————————-|
| 1 | Add meta to cart item | `woocommerce_add_cart_item_data` |
| 2 | Display in cart (optional) | `woocommerce_get_item_data` |
| 3 | Save meta to order item | `woocommerce_add_order_item_meta` (WC < 4.4) **OR**`woocommerce_checkout_create_order_line_item` (WC β₯ 4.4) |
—
## β Full Working Example
Letβs say you're saving this custom meta in the cart:
add_filter('woocommerce_add_cart_item_data', function($cart_item_data, $product_id) {
if (isset($_GET['probo_configure_meta'])) {
$decoded = json_decode(base64_decode(sanitize_text_field($_GET['probo_configure_meta'])), true);
if ($decoded && is_array($decoded)) {
$cart_item_data['probo_configure_meta'] = $decoded;
// Optional: add direct shortcut fields for convenience
if (!empty($decoded['selected_delivery'])) {
$cart_item_data['probo_delivery_hours'] = $decoded['selected_delivery'] . 'h';
}
}
}
return $cart_item_data;
}, 10, 2);
—
### β Display Custom Meta in Cart (Optional)
add_filter('woocommerce_get_item_data', function($item_data, $cart_item) {
if (!empty($cart_item['probo_delivery_hours'])) {
$item_data[] = [
'name' => 'Delivery Time',
'value' => $cart_item['probo_delivery_hours']
];
}
return $item_data;
}, 10, 2);
—
### β Save to Order Item (WC 4.4+ Safe Way)
add_action('woocommerce_checkout_create_order_line_item', function($item, $cart_item_key, $values, $order) {
if (!empty($values['probo_configure_meta'])) {
$meta = $values['probo_configure_meta'];
// Save full config
$item->add_meta_data('probo_configure_meta', $meta);
// Save delivery hours as separate meta
if (!empty($meta['selected_delivery'])) {
$item->add_meta_data('probo_delivery_hours', $meta['selected_delivery'] . 'h');
}
}
}, 10, 4);
β Works safely in all WooCommerce versions.
—
### π How to View Saved Meta
In order item line meta:
$meta = wc_get_order_item_meta($item_id, 'probo_configure_meta');
$delivery = wc_get_order_item_meta($item_id, 'probo_delivery_hours');
—
### π― Bonus: Show in Admin Interface
add_action('woocommerce_after_order_itemmeta', function($item_id, $item, $product){
$delivery = wc_get_order_item_meta($item_id, 'probo_delivery_hours');
if ($delivery) {
echo '<p><strong>Probo Delivery:</strong> ' . esc_html($delivery) . '</p>';
}
}, 10, 3);
—
Let me know if you'd like to:
– Save JSON pretty format
– Access from REST API
– Show in customer emails or My Account > Orders
Iβll help with that next!
show above array data in php line by line
Hereβs a full example that handles your structure:
—
### β PHP Code to Display Each Selected Option:
$selected_options = get_post_meta($order_item_id, 'probo_configure_meta', true)['selected_options'] ?? [];
if (!empty($selected_options)) {
echo '<h4>Probo Selected Options:</h4>';
echo '<ul>';
foreach ($selected_options as $option) {
echo '<li>';
echo '<strong>' . esc_html($option['name'] ?? '-') . '</strong>';
if (!empty($option['value'])) {
echo ': ' . esc_html($option['value']);
} elseif (!empty($option['description'])) {
echo ' β ' . esc_html($option['description']);
}
// If there's an image
if (!empty($option['images'][0]['url'])) {
echo '<br><img src="' . esc_url($option['images'][0]['url']) . '" alt="" width="60">';
}
// Extra detail like parent
if (!empty($option['parent_name'])) {
echo '<br><small>Group: ' . esc_html($option['parent_name']) . '</small>';
}
echo '</li>';
}
echo '</ul>';
}
—
### π§Ύ Example Output (in admin/email/frontend):
β
Probo Selected Options:
β’ Width: 25
β’ Height: 15
β’ Amount: 3
β’ Single-sided β [image]
β’ Banner 510 β PVC banner suited for both indoor and outdoor applications. [image]
β’ Use same finish for all edges β All edges of the banner receive the same finish. [image]
β’ Cut β The material is cut to the desired size. [image]
—
### β Optional Styling Tips (for email or frontend):
Wrap inside a div for better control:
echo '<div class="probo-options" style="font-size:14px;line-height:1.6;">';
...
echo '</div>';
—
Let me know if you want to:
– Format this into a table instead of a list
– Output only certain option types (e.g., only `radio`)
– Show grouped by parent category (like "Size", "Material", etc.)