{"id":7296,"date":"2025-08-18T12:17:44","date_gmt":"2025-08-18T19:17:44","guid":{"rendered":"https:\/\/www.ultimatewb.com\/blog\/?p=7296"},"modified":"2025-08-18T12:17:44","modified_gmt":"2025-08-18T19:17:44","slug":"how-to-add-order-a-print-to-a-custom-website-printful-stripe-paypal-php","status":"publish","type":"post","link":"https:\/\/www.ultimatewb.com\/blog\/7296\/how-to-add-order-a-print-to-a-custom-website-printful-stripe-paypal-php\/","title":{"rendered":"How to Add \u201cOrder a Print\u201d to a Custom Website (Printful + Stripe + PayPal + PHP)"},"content":{"rendered":"\n<figure class=\"wp-block-image size-large\"><a href=\"https:\/\/www.ultimatewb.com\/blog\/wp-content\/uploads\/printful-stripe-paypal-order-prints-setup.jpg\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"1024\" src=\"https:\/\/www.ultimatewb.com\/blog\/wp-content\/uploads\/printful-stripe-paypal-order-prints-setup-1024x1024.jpg\" alt=\"Printful, Stripe, PayPal integration in custom website via PHP, customer order prints setup\" class=\"wp-image-7299\" srcset=\"https:\/\/www.ultimatewb.com\/blog\/wp-content\/uploads\/printful-stripe-paypal-order-prints-setup-1024x1024.jpg 1024w, https:\/\/www.ultimatewb.com\/blog\/wp-content\/uploads\/printful-stripe-paypal-order-prints-setup-300x300.jpg 300w, https:\/\/www.ultimatewb.com\/blog\/wp-content\/uploads\/printful-stripe-paypal-order-prints-setup-150x150.jpg 150w, https:\/\/www.ultimatewb.com\/blog\/wp-content\/uploads\/printful-stripe-paypal-order-prints-setup-768x768.jpg 768w, https:\/\/www.ultimatewb.com\/blog\/wp-content\/uploads\/printful-stripe-paypal-order-prints-setup.jpg 1200w\" sizes=\"(max-width: 600px) 100vw, (max-width: 1200px) 75vw, 1200px\" \/><\/a><\/figure>\n\n\n\n<p>Want to turn your photos, artwork, drawings, or paintings into ready-to-order prints? In this tutorial, I\u2019ll show you how to add a simple <strong>\u201cOrder a Print\u201d<\/strong> button to your website, connect it with <strong>Printful<\/strong> to handle fulfillment, and accept payments through <strong>Stripe or PayPal<\/strong>. Earlier we released a <a href=\"https:\/\/www.ultimatewb.com\/blog\/5216\/tutorial-easiest-way-to-integrate-your-custom-website-with-printful\/\">tutorial on how to integrate with Printful using what we call &#8220;Method A&#8221;<\/a>, where your items are on the Printful platform and you use the API to display your items on your website.<\/p>\n\n\n\n<p>You\u2019ll get:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>PHP pages you can integrate into your current site (index\/item\/checkout).<\/li>\n\n\n\n<li>Stripe Checkout flow (with webhook) and PayPal Buttons flow.<\/li>\n\n\n\n<li>A clear function that creates &amp; confirms a Printful order using the image URL and size selected on your page.<\/li>\n\n\n\n<li>A simple size\u2192variant map so you control which Printful product\/variant is used for each size.<\/li>\n<\/ul>\n\n\n\n<p>If you prefer a ready-to-run starter folder with all files from this tutorial, grab this:<br><strong><a href=\"https:\/\/www.ultimatewb.com\/docs\/printful-php-starter-kit.zip\">Download the ZIP<\/a><\/strong> &#8211; we are using the example of &#8220;paintings&#8221; for simplicity.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>1) What you\u2019re building &#8211; flow overview<\/strong><\/h2>\n\n\n\n<p>A visitor opens your artwork\/photo\/drawing page \u2192 clicks <strong>Order Print<\/strong><\/p>\n\n\n\n<p>They choose size and quantity, then land on Checkout<\/p>\n\n\n\n<p>They fill shipping details, pay via Stripe or PayPal<\/p>\n\n\n\n<p>On successful payment, your server calls Printful API to create and confirm the order (with the image URL + chosen size).<\/p>\n\n\n\n<p>Printful fulfills the order and sends tracking updates to the customer\u2019s email.<\/p>\n\n\n\n<p>We\u2019re using &#8220;Method B&#8221; (No Sync\/Store): you don\u2019t pre-create products in Printful. Instead, you send the image URL + variant ID (size\/material) on the fly when placing the order.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>2) Prerequisites you\u2019ll need<\/strong><\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>PHP 7.4+ with cURL enabled<\/li>\n\n\n\n<li>Your <strong>Printful API key<\/strong><\/li>\n\n\n\n<li>A <strong>Stripe<\/strong> account + test API keys<\/li>\n\n\n\n<li>A <strong>PayPal<\/strong> (Sandbox first) Client ID\/Secret<\/li>\n\n\n\n<li>HTTPS on production (required for Stripe\/PayPal in live)<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>3) Project structure<\/strong><\/h2>\n\n\n\n<p>You can merge this into your existing site. For the tutorial, we\u2019ll assume this structure:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>your-site\/\n  config.php                 # all keys\/settings + size\u2192variant map\n  data\/paintings.php         # your painting data (can swap for DB)\n  src\/lib\/helpers.php        # helpers: HTTP, Printful, Stripe, PayPal\n  public\/\n    index.php                # sample gallery (optional)\n    painting.php             # painting detail with \u201cOrder Print\u201d form\n    checkout.php             # collects shipping + triggers payment\n    stripe_create_checkout.php\n    stripe_webhook.php\n    paypal_create_order.php\n    paypal_capture.php\n    place_printful_after_payment.php\n    thankyou.php\n    cancel.php\n  logs\/app.log               # runtime logs (optional)\n<\/code><\/pre>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p>If you downloaded the ZIP, it already has this structure.<\/p>\n<\/blockquote>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>4) Configure your credentials (<code>config.php<\/code>)<\/strong><\/h2>\n\n\n\n<p>Create <code>config.php<\/code> at the project root:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;?php\n\/\/ ====== CONFIG ======\n\/\/ Fill these with your real credentials.\n\n\/\/ Printful\ndefine(\"PRINTFUL_API_KEY\", \"REPLACE_WITH_YOUR_PRINTFUL_API_KEY\");\ndefine(\"PRINTFUL_API_BASE\", \"https:\/\/api.printful.com\");\n\n\/\/ Stripe\ndefine(\"STRIPE_SECRET_KEY\", \"sk_test_xxx\");       \/\/ get from https:\/\/dashboard.stripe.com\/test\/apikeys\ndefine(\"STRIPE_WEBHOOK_SECRET\", \"whsec_xxx\");     \/\/ from your Stripe webhook endpoint\n\n\/\/ PayPal (Sandbox first)\ndefine(\"PAYPAL_CLIENT_ID\", \"YOUR_PAYPAL_CLIENT_ID\");\ndefine(\"PAYPAL_CLIENT_SECRET\", \"YOUR_PAYPAL_CLIENT_SECRET\");\ndefine(\"PAYPAL_API_BASE\", \"https:\/\/api-m.sandbox.paypal.com\"); \/\/ switch to live later\n\n\/\/ App\ndefine(\"APP_BASE_URL\", \"http:\/\/localhost:8000\"); \/\/ your domain in prod, no trailing slash\ndefine(\"APP_LOG_FILE\", __DIR__ . \"\/logs\/app.log\");\n\n\/\/ Size \u2192 Printful variant map (EXAMPLE: posters). Replace with real variant IDs.\n$SIZE_MAP = &#91;\n  \"12x18\" =&gt; 4011,\n  \"18x24\" =&gt; 4013,\n  \"24x36\" =&gt; 4015\n];\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>About the <code>SIZE_MAP<\/code><\/strong><\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>The keys are your <strong>public sizes<\/strong> (what the user picks), like <code>\"12x18\"<\/code>.<\/li>\n\n\n\n<li>The values are <strong>Printful variant IDs<\/strong> that correspond to a specific base product+size (e.g., poster 12\u00d718).<\/li>\n\n\n\n<li>Replace the example IDs with the real ones from the Printful Catalog for the product\/material you want (posters, canvases, etc.). You can add\/remove sizes per painting.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>5) Add your paintings data (<code>data\/paintings.php<\/code>)<\/strong><\/h2>\n\n\n\n<p>You probably already have this in a database; here\u2019s a simple PHP array you can swap out later:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;?php\n\/\/ Example dataset. Replace with your real data source (DB).\n$PAINTINGS = &#91;\n  &#91;\n    \"id\" =&gt; 1,\n    \"slug\" =&gt; \"sunset-sky\",\n    \"title\" =&gt; \"Sunset Sky\",\n    \"image_url\" =&gt; \"https:\/\/via.placeholder.com\/1200x1600.png?text=Sunset+Sky\",\n    \"sizes\" =&gt; &#91;\"12x18\",\"18x24\",\"24x36\"],\n    \"price_map\" =&gt; &#91;\"12x18\"=&gt;2999, \"18x24\"=&gt;4999, \"24x36\"=&gt;7999] \/\/ USD cents\n  ],\n  &#91;\n    \"id\" =&gt; 2,\n    \"slug\" =&gt; \"blue-forest\",\n    \"title\" =&gt; \"Blue Forest\",\n    \"image_url\" =&gt; \"https:\/\/via.placeholder.com\/1200x1600.png?text=Blue+Forest\",\n    \"sizes\" =&gt; &#91;\"12x18\",\"18x24\"],\n    \"price_map\" =&gt; &#91;\"12x18\"=&gt;2999, \"18x24\"=&gt;4999]\n  ]\n];\n<\/code><\/pre>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>image_url<\/code> should point to your high-resolution artwork image online (Printful will fetch it to print).<\/li>\n\n\n\n<li><code>sizes<\/code> controls which sizes appear on each painting page.<\/li>\n\n\n\n<li><code>price_map<\/code> is your retail pricing, per size (in cents).<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>6) Helper functions (<code>src\/lib\/helpers.php<\/code>)<\/strong><\/h2>\n\n\n\n<p>This file centralizes:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>HTTP requests to Printful<\/li>\n\n\n\n<li>Stripe Checkout requests (without SDK for simplicity)<\/li>\n\n\n\n<li>PayPal REST requests<\/li>\n\n\n\n<li>A single function to <strong>place &amp; confirm<\/strong> a Printful order<\/li>\n<\/ul>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;?php\nrequire_once __DIR__ . \"\/..\/..\/config.php\";\n\nfunction app_log($message) {\n  $line = sprintf(\"&#91;%s] %s\\n\", date(\"c\"), $message);\n  file_put_contents(APP_LOG_FILE, $line, FILE_APPEND);\n}\n\nfunction h($s) { return htmlspecialchars($s ?? \"\", ENT_QUOTES, 'UTF-8'); }\n\nfunction require_post($keys) {\n  foreach ($keys as $k) {\n    if (!isset($_POST&#91;$k]) || $_POST&#91;$k] === \"\") {\n      http_response_code(422);\n      echo \"Missing field: \" . h($k);\n      exit;\n    }\n  }\n}\n\nfunction printful_request($endpoint, $method=\"GET\", $data=null) {\n  $url = rtrim(PRINTFUL_API_BASE, \"\/\") . $endpoint;\n  $ch = curl_init($url);\n  curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);\n  curl_setopt($ch, CURLOPT_HTTPHEADER, &#91;\n    \"Authorization: Bearer \" . PRINTFUL_API_KEY,\n    \"Content-Type: application\/json\"\n  ]);\n  if ($method === \"POST\") {\n    curl_setopt($ch, CURLOPT_POST, true);\n    if (!is_null($data)) curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));\n  } elseif ($method === \"DELETE\") {\n    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, \"DELETE\");\n  }\n  $resp = curl_exec($ch);\n  if ($resp === false) {\n    app_log(\"cURL error: \" . curl_error($ch));\n    curl_close($ch);\n    return null;\n  }\n  curl_close($ch);\n  return json_decode($resp, true);\n}\n\n\/\/ Stripe minimal HTTP (no SDK for simplicity)\nfunction stripe_request($path, $fields) {\n  $url = \"https:\/\/api.stripe.com\" . $path;\n  $ch = curl_init($url);\n  curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);\n  curl_setopt($ch, CURLOPT_USERPWD, STRIPE_SECRET_KEY . \":\");\n  curl_setopt($ch, CURLOPT_POST, true);\n  curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($fields));\n  $resp = curl_exec($ch);\n  if ($resp === false) {\n    app_log(\"Stripe cURL error: \" . curl_error($ch));\n    curl_close($ch);\n    return null;\n  }\n  curl_close($ch);\n  return json_decode($resp, true);\n}\n\nfunction verify_stripe_sig($payload, $header, $secret) {\n  \/\/ Minimal verification. In production, use Stripe's official library.\n  $parts = &#91;];\n  foreach (explode(\",\", $header) as $kv) {\n    $pair = array_map(\"trim\", explode(\"=\", $kv, 2));\n    if (count($pair) === 2) $parts&#91;$pair&#91;0]] = $pair&#91;1];\n  }\n  if (!isset($parts&#91;\"t\"]) || !isset($parts&#91;\"v1\"])) return false;\n  $signed_payload = $parts&#91;\"t\"] . \".\" . $payload;\n  $computed = hash_hmac(\"sha256\", $signed_payload, $secret);\n  return hash_equals($computed, $parts&#91;\"v1\"]);\n}\n\n\/\/ PayPal helpers\nfunction paypal_access_token() {\n  $ch = curl_init(PAYPAL_API_BASE . \"\/v1\/oauth2\/token\");\n  curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);\n  curl_setopt($ch, CURLOPT_USERPWD, PAYPAL_CLIENT_ID . \":\" . PAYPAL_CLIENT_SECRET);\n  curl_setopt($ch, CURLOPT_POST, true);\n  curl_setopt($ch, CURLOPT_POSTFIELDS, \"grant_type=client_credentials\");\n  $resp = curl_exec($ch);\n  if ($resp === false) return null;\n  curl_close($ch);\n  $data = json_decode($resp, true);\n  return $data&#91;\"access_token\"] ?? null;\n}\n\nfunction paypal_request($method, $path, $data=null, $token=null) {\n  $ch = curl_init(PAYPAL_API_BASE . $path);\n  curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);\n  curl_setopt($ch, CURLOPT_HTTPHEADER, &#91;\n    \"Authorization: Bearer \" . $token,\n    \"Content-Type: application\/json\"\n  ]);\n  if ($method === \"POST\") {\n    curl_setopt($ch, CURLOPT_POST, true);\n    if (!is_null($data)) curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));\n  }\n  $resp = curl_exec($ch);\n  if ($resp === false) return null;\n  curl_close($ch);\n  return json_decode($resp, true);\n}\n\n\/\/ Create &amp; confirm a Printful order\nfunction place_printful_order($customer, $item, $sizeMap) {\n  \/\/ $customer: &#91;name,address1,city,state_code,country_code,zip,email]\n  \/\/ $item: &#91;title,image_url,size,quantity]\n  $size = $item&#91;\"size\"];\n  if (!isset($sizeMap&#91;$size])) {\n    throw new Exception(\"Unknown size variant: \" . $size);\n  }\n  $variant_id = $sizeMap&#91;$size];\n\n  $payload = &#91;\n    \"external_id\" =&gt; \"@ORDER-\" . time() . \"-\" . rand(1000,9999),\n    \"recipient\" =&gt; &#91;\n      \"name\" =&gt; $customer&#91;\"name\"],\n      \"address1\" =&gt; $customer&#91;\"address1\"],\n      \"city\" =&gt; $customer&#91;\"city\"],\n      \"state_code\" =&gt; $customer&#91;\"state_code\"],\n      \"country_code\" =&gt; $customer&#91;\"country_code\"],\n      \"zip\" =&gt; $customer&#91;\"zip\"],\n      \"email\" =&gt; $customer&#91;\"email\"]\n    ],\n    \"items\" =&gt; &#91;&#91;\n      \"variant_id\" =&gt; $variant_id,\n      \"quantity\" =&gt; intval($item&#91;\"quantity\"] ?? 1),\n      \"name\" =&gt; $item&#91;\"title\"],\n      \"files\" =&gt; &#91;&#91; \"url\" =&gt; $item&#91;\"image_url\"] ]]\n    ]]\n  ];\n\n  $create = printful_request(\"\/orders\", \"POST\", $payload);\n  if (!$create || !isset($create&#91;\"result\"]&#91;\"id\"])) {\n    app_log(\"Printful order create failed: \" . json_encode($create));\n    return null;\n  }\n  $order_id = $create&#91;\"result\"]&#91;\"id\"];\n\n  \/\/ Confirm to start fulfillment\n  $confirm = printful_request(\"\/orders\/\" . $order_id . \"\/confirm\", \"POST\", &#91;]);\n  return &#91;\"create\" =&gt; $create, \"confirm\" =&gt; $confirm];\n}\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>7) Painting page: add the button &amp; form (<code>public\/painting.php<\/code>)<\/strong><\/h2>\n\n\n\n<p>On each painting page, show the image and available sizes. When the customer clicks \u201cOrder Print,\u201d the form sends their selection to checkout.php via POST:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;?php\nrequire_once __DIR__ . \"\/..\/config.php\";\nrequire_once __DIR__ . \"\/..\/data\/paintings.php\";\n\n$slug = $_GET&#91;'slug'] ?? '';\n$painting = null;\nforeach ($PAINTINGS as $p) {\n  if ($p&#91;'slug'] === $slug) { $painting = $p; break; }\n}\nif (!$painting) { http_response_code(404); echo \"Not found\"; exit; }\n?&gt;\n&lt;!doctype html&gt;\n&lt;html&gt;\n&lt;head&gt;\n  &lt;meta charset=\"utf-8\" \/&gt;\n  &lt;title&gt;&lt;?= htmlspecialchars($painting&#91;'title']) ?&gt;&lt;\/title&gt;\n  &lt;meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"&gt;\n  &lt;style&gt;\n    body { font-family: system-ui, Arial, sans-serif; margin: 2rem; max-width: 900px; }\n    img { width: 100%; height: auto; border-radius: 8px; }\n    form { margin-top: 1rem; display: grid; gap: .75rem; }\n    input, select { padding: .6rem; font-size: 1rem; }\n    .btn { padding:.7rem 1rem; border-radius:8px; border:1px solid #333; background:#111; color:#fff; }\n  &lt;\/style&gt;\n&lt;\/head&gt;\n&lt;body&gt;\n  &lt;a href=\"index.php\"&gt;&amp;larr; All paintings&lt;\/a&gt;\n  &lt;h1&gt;&lt;?= htmlspecialchars($painting&#91;'title']) ?&gt;&lt;\/h1&gt;\n  &lt;img src=\"&lt;?= htmlspecialchars($painting&#91;'image_url']) ?&gt;\" alt=\"&lt;?= htmlspecialchars($painting&#91;'title']) ?&gt;\"&gt;\n\n  &lt;form action=\"checkout.php\" method=\"post\"&gt;\n    &lt;input type=\"hidden\" name=\"title\" value=\"&lt;?= htmlspecialchars($painting&#91;'title']) ?&gt;\"&gt;\n    &lt;input type=\"hidden\" name=\"image_url\" value=\"&lt;?= htmlspecialchars($painting&#91;'image_url']) ?&gt;\"&gt;\n\n    &lt;label&gt;Size&lt;\/label&gt;\n    &lt;select name=\"size\" required&gt;\n      &lt;?php foreach ($painting&#91;'sizes'] as $size): ?&gt;\n        &lt;option value=\"&lt;?= htmlspecialchars($size) ?&gt;\"&gt;&lt;?= htmlspecialchars($size) ?&gt;&lt;\/option&gt;\n      &lt;?php endforeach; ?&gt;\n    &lt;\/select&gt;\n\n    &lt;label&gt;Quantity&lt;\/label&gt;\n    &lt;input type=\"number\" name=\"quantity\" min=\"1\" value=\"1\" required&gt;\n\n    &lt;button class=\"btn\" type=\"submit\"&gt;Order Print&lt;\/button&gt;\n  &lt;\/form&gt;\n&lt;\/body&gt;\n&lt;\/html&gt;\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>8) Checkout: collect shipping + trigger payment (<code>public\/checkout.php<\/code>)<\/strong><\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Shows order summary &amp; shipping form.<\/li>\n\n\n\n<li>Offers <strong>Stripe (card)<\/strong> or <strong>PayPal<\/strong>.<\/li>\n\n\n\n<li>Passes the painting details (<code>title<\/code>, <code>image_url<\/code>, <code>size<\/code>, <code>quantity<\/code>) along.<\/li>\n<\/ul>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;?php\nrequire_once __DIR__ . \"\/..\/config.php\";\nrequire_once __DIR__ . \"\/..\/data\/paintings.php\";\n\n$title = $_POST&#91;'title'] ?? null;\n$image_url = $_POST&#91;'image_url'] ?? null;\n$size = $_POST&#91;'size'] ?? null;\n$quantity = max(1, intval($_POST&#91;'quantity'] ?? 1));\n\nif (!$title || !$image_url || !$size) { http_response_code(422); echo \"Missing fields\"; exit; }\n\n\/\/ Determine price from your data\n$price_cents = 4999;\nforeach ($PAINTINGS as $p) {\n  if ($p&#91;\"title\"] === $title &amp;&amp; isset($p&#91;\"price_map\"]&#91;$size])) {\n    $price_cents = intval($p&#91;\"price_map\"]&#91;$size]);\n    break;\n  }\n}\n$total_cents = $price_cents * $quantity;\n?&gt;\n&lt;!doctype html&gt;\n&lt;html&gt;\n&lt;head&gt;\n  &lt;meta charset=\"utf-8\" \/&gt;\n  &lt;title&gt;Checkout - &lt;?= htmlspecialchars($title) ?&gt;&lt;\/title&gt;\n  &lt;meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"&gt;\n  &lt;script src=\"https:\/\/js.stripe.com\/v3\/\"&gt;&lt;\/script&gt;\n  &lt;script src=\"https:\/\/www.paypal.com\/sdk\/js?client-id=&lt;?= htmlspecialchars(PAYPAL_CLIENT_ID) ?&gt;&amp;currency=USD\"&gt;&lt;\/script&gt;\n  &lt;style&gt;\n    body { font-family: system-ui, Arial, sans-serif; margin: 2rem; max-width: 900px; }\n    .grid { display:grid; grid-template-columns: 1fr 1fr; gap: 2rem; }\n    .card { border: 1px solid #ddd; border-radius: 12px; padding: 1rem; }\n    input, select { width:100%; padding:.6rem; margin:.3rem 0; }\n    .btn { padding:.7rem 1rem; border-radius:8px; border:1px solid #333; background:#111; color:#fff; }\n    #paypal-button-container { margin-top: 1rem; }\n  &lt;\/style&gt;\n&lt;\/head&gt;\n&lt;body&gt;\n  &lt;a href=\"javascript:history.back()\"&gt;&amp;larr; Back&lt;\/a&gt;\n  &lt;h1&gt;Checkout&lt;\/h1&gt;\n  &lt;div class=\"grid\"&gt;\n    &lt;div class=\"card\"&gt;\n      &lt;h3&gt;Order&lt;\/h3&gt;\n      &lt;p&gt;&lt;strong&gt;&lt;?= htmlspecialchars($title) ?&gt;&lt;\/strong&gt;&lt;\/p&gt;\n      &lt;p&gt;Size: &lt;?= htmlspecialchars($size) ?&gt; \u00d7 Qty: &lt;?= htmlspecialchars($quantity) ?&gt;&lt;\/p&gt;\n      &lt;p&gt;Total: $&lt;?= number_format($total_cents\/100, 2) ?&gt;&lt;\/p&gt;\n      &lt;img src=\"&lt;?= htmlspecialchars($image_url) ?&gt;\" style=\"max-width:100%;border-radius:8px\" alt=\"\"&gt;\n    &lt;\/div&gt;\n    &lt;div class=\"card\"&gt;\n      &lt;h3&gt;Shipping details&lt;\/h3&gt;\n      &lt;form id=\"customer-form\"&gt;\n        &lt;input name=\"name\" placeholder=\"Full name\" required&gt;\n        &lt;input name=\"email\" type=\"email\" placeholder=\"Email\" required&gt;\n        &lt;input name=\"address1\" placeholder=\"Address line 1\" required&gt;\n        &lt;input name=\"city\" placeholder=\"City\" required&gt;\n        &lt;input name=\"state_code\" placeholder=\"State (e.g., CA)\" required&gt;\n        &lt;input name=\"zip\" placeholder=\"ZIP\" required&gt;\n        &lt;input name=\"country_code\" placeholder=\"Country (e.g., US)\" value=\"US\" required&gt;\n      &lt;\/form&gt;\n\n      &lt;h3&gt;Pay with Stripe&lt;\/h3&gt;\n      &lt;button class=\"btn\" id=\"stripe-btn\"&gt;Pay with Card&lt;\/button&gt;\n\n      &lt;h3 style=\"margin-top:1.5rem;\"&gt;Or Pay with PayPal&lt;\/h3&gt;\n      &lt;div id=\"paypal-button-container\"&gt;&lt;\/div&gt;\n    &lt;\/div&gt;\n  &lt;\/div&gt;\n\n&lt;script&gt;\nconst totalCents = &lt;?= json_encode($total_cents) ?&gt;;\nconst item = &lt;?= json_encode(&#91;\"title\"=&gt;$title, \"image_url\"=&gt;$image_url, \"size\"=&gt;$size, \"quantity\"=&gt;$quantity]) ?&gt;;\n\nfunction formDataObj(form) {\n  const fd = new FormData(form);\n  const o = {};\n  for (const &#91;k,v] of fd.entries()) o&#91;k]=v;\n  return o;\n}\n\n\/\/ Stripe Checkout\ndocument.getElementById(\"stripe-btn\").addEventListener(\"click\", async () =&gt; {\n  const customer = formDataObj(document.getElementById(\"customer-form\"));\n  const resp = await fetch(\"stripe_create_checkout.php\", {\n    method: \"POST\",\n    headers: {\"Content-Type\": \"application\/json\"},\n    body: JSON.stringify({ amount_cents: totalCents, item, customer })\n  });\n  const data = await resp.json();\n  if (!data.id || !data.url) { alert(\"Stripe error\"); console.error(data); return; }\n  window.location = data.url;\n});\n\n\/\/ PayPal Buttons\npaypal.Buttons({\n  createOrder: async function(data, actions) {\n    const customer = formDataObj(document.getElementById(\"customer-form\"));\n    const resp = await fetch(\"paypal_create_order.php\", {\n      method: \"POST\",\n      headers: {\"Content-Type\": \"application\/json\"},\n      body: JSON.stringify({ amount_cents: totalCents, item, customer })\n    });\n    const out = await resp.json();\n    return out.id; \/\/ PayPal order ID\n  },\n  onApprove: async function(data, actions) {\n    const resp = await fetch(\"paypal_capture.php\", {\n      method: \"POST\",\n      headers: {\"Content-Type\": \"application\/json\"},\n      body: JSON.stringify({ orderID: data.orderID })\n    });\n    const out = await resp.json();\n    if (out.status === \"COMPLETED\") {\n      \/\/ Place Printful order after capture\n      const place = await fetch(\"place_printful_after_payment.php\", {\n        method: \"POST\",\n        headers: {\"Content-Type\":\"application\/json\"},\n        body: JSON.stringify({ capture: out })\n      });\n      const placed = await place.json();\n      alert(\"Order placed! Check your email.\");\n      window.location = \"thankyou.php\";\n    } else {\n      alert(\"Payment not completed.\");\n    }\n  }\n}).render('#paypal-button-container');\n&lt;\/script&gt;\n&lt;\/body&gt;\n&lt;\/html&gt;\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>9) Stripe: create session + webhook<\/strong><\/h2>\n\n\n\n<h3 class=\"wp-block-heading\"><code>public\/stripe_create_checkout.php<\/code><\/h3>\n\n\n\n<p>Creates a Checkout Session and includes your item info in metadata.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;?php\nrequire_once __DIR__ . \"\/..\/config.php\";\nrequire_once __DIR__ . \"\/..\/src\/lib\/helpers.php\";\n\n$payload = json_decode(file_get_contents(\"php:\/\/input\"), true);\n$amount = intval($payload&#91;\"amount_cents\"] ?? 0);\n$item = $payload&#91;\"item\"] ?? &#91;];\n$customer = $payload&#91;\"customer\"] ?? &#91;];\n\nif ($amount &lt;= 0) { http_response_code(400); echo json_encode(&#91;\"error\"=&gt;\"bad amount\"]); exit; }\n\n$domain = APP_BASE_URL;\n$metadata = &#91;\n  \"title\" =&gt; $item&#91;\"title\"] ?? \"\",\n  \"image_url\" =&gt; $item&#91;\"image_url\"] ?? \"\",\n  \"size\" =&gt; $item&#91;\"size\"] ?? \"\",\n  \"quantity\" =&gt; (string)($item&#91;\"quantity\"] ?? 1),\n  \"customer_email\" =&gt; $customer&#91;\"email\"] ?? \"\"\n];\n\n$session = stripe_request(\"\/v1\/checkout\/sessions\", &#91;\n  \"mode\" =&gt; \"payment\",\n  \"success_url\" =&gt; $domain . \"\/public\/thankyou.php?session_id={CHECKOUT_SESSION_ID}\",\n  \"cancel_url\" =&gt; $domain . \"\/public\/cancel.php\",\n  \"payment_method_types&#91;]\" =&gt; \"card\",\n  \"line_items&#91;0]&#91;price_data]&#91;currency]\" =&gt; \"usd\",\n  \"line_items&#91;0]&#91;price_data]&#91;product_data]&#91;name]\" =&gt; $item&#91;\"title\"] ?? \"Art Print\",\n  \"line_items&#91;0]&#91;price_data]&#91;unit_amount]\" =&gt; $amount,\n  \"line_items&#91;0]&#91;quantity]\" =&gt; 1,\n  \"metadata&#91;title]\" =&gt; $metadata&#91;\"title\"],\n  \"metadata&#91;image_url]\" =&gt; $metadata&#91;\"image_url\"],\n  \"metadata&#91;size]\" =&gt; $metadata&#91;\"size\"],\n  \"metadata&#91;quantity]\" =&gt; $metadata&#91;\"quantity\"],\n  \"customer_email\" =&gt; $customer&#91;\"email\"] ?? null\n]);\n\nheader(\"Content-Type: application\/json\");\necho json_encode($session ?? &#91;\"error\"=&gt;\"stripe failed\"]);\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\"><code>public\/stripe_webhook.php<\/code><\/h3>\n\n\n\n<p>On <code>checkout.session.completed<\/code>, we reconstruct the customer\/item and <strong>place the Printful order<\/strong>.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;?php\nrequire_once __DIR__ . \"\/..\/config.php\";\nrequire_once __DIR__ . \"\/..\/src\/lib\/helpers.php\";\n\n$payload = file_get_contents(\"php:\/\/input\");\n$sig = $_SERVER&#91;\"HTTP_STRIPE_SIGNATURE\"] ?? \"\";\n\nif (!verify_stripe_sig($payload, $sig, STRIPE_WEBHOOK_SECRET)) {\n  http_response_code(400);\n  echo \"Invalid signature\";\n  exit;\n}\n\n$event = json_decode($payload, true);\n$type = $event&#91;\"type\"] ?? \"\";\n\nif ($type === \"checkout.session.completed\") {\n  $session = $event&#91;\"data\"]&#91;\"object\"];\n  $metadata = $session&#91;\"metadata\"] ?? &#91;];\n  \/\/ Build customer &amp; item from metadata (you can also expand line items via Stripe API)\n  $customer = &#91;\n    \"name\" =&gt; $session&#91;\"customer_details\"]&#91;\"name\"] ?? \"Customer\",\n    \"email\" =&gt; $session&#91;\"customer_details\"]&#91;\"email\"] ?? ($metadata&#91;\"customer_email\"] ?? \"\"),\n    \"address1\" =&gt; $session&#91;\"customer_details\"]&#91;\"address\"]&#91;\"line1\"] ?? \"Address\",\n    \"city\" =&gt; $session&#91;\"customer_details\"]&#91;\"address\"]&#91;\"city\"] ?? \"\",\n    \"state_code\" =&gt; $session&#91;\"customer_details\"]&#91;\"address\"]&#91;\"state\"] ?? \"\",\n    \"country_code\" =&gt; $session&#91;\"customer_details\"]&#91;\"address\"]&#91;\"country\"] ?? \"US\",\n    \"zip\" =&gt; $session&#91;\"customer_details\"]&#91;\"address\"]&#91;\"postal_code\"] ?? \"\"\n  ];\n  $item = &#91;\n    \"title\" =&gt; $metadata&#91;\"title\"] ?? \"Art Print\",\n    \"image_url\" =&gt; $metadata&#91;\"image_url\"] ?? \"\",\n    \"size\" =&gt; $metadata&#91;\"size\"] ?? \"18x24\",\n    \"quantity\" =&gt; intval($metadata&#91;\"quantity\"] ?? 1)\n  ];\n  global $SIZE_MAP;\n  try {\n    $placed = place_printful_order($customer, $item, $SIZE_MAP);\n    app_log(\"Stripe webhook Printful order placed: \" . json_encode($placed));\n  } catch (Exception $e) { app_log(\"Printful order error: \" . $e-&gt;getMessage()); }\n}\n\nhttp_response_code(200);\necho \"ok\";\n<\/code><\/pre>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p>In Stripe Dashboard, add a webhook endpoint pointing to <code>https:\/\/YOURDOMAIN.com\/public\/stripe_webhook.php<\/code> listening to <strong><code>checkout.session.completed<\/code><\/strong>, and copy the signing secret into <code>config.php<\/code>.<\/p>\n<\/blockquote>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>10) PayPal: create order + capture + place Printful<\/strong><\/h2>\n\n\n\n<h3 class=\"wp-block-heading\"><code>public\/paypal_create_order.php<\/code><\/h3>\n\n\n\n<p>Creates a PayPal order for the amount.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;?php\nrequire_once __DIR__ . \"\/..\/config.php\";\nrequire_once __DIR__ . \"\/..\/src\/lib\/helpers.php\";\n\n$payload = json_decode(file_get_contents(\"php:\/\/input\"), true);\n$amount = intval($payload&#91;\"amount_cents\"] ?? 0) \/ 100.0;\n$item = $payload&#91;\"item\"] ?? &#91;];\n$customer = $payload&#91;\"customer\"] ?? &#91;];\n\n$token = paypal_access_token();\n$order = paypal_request(\"POST\", \"\/v2\/checkout\/orders\", &#91;\n  \"intent\" =&gt; \"CAPTURE\",\n  \"purchase_units\" =&gt; &#91;&#91;\n    \"amount\" =&gt; &#91; \"currency_code\" =&gt; \"USD\", \"value\" =&gt; number_format($amount, 2, \".\", \"\") ],\n    \"description\" =&gt; $item&#91;\"title\"] ?? \"Art Print\"\n  ]],\n  \"payer\" =&gt; &#91;\n    \"email_address\" =&gt; $customer&#91;\"email\"] ?? null,\n    \"name\" =&gt; &#91; \"given_name\" =&gt; $customer&#91;\"name\"] ?? \"Customer\" ]\n  ],\n  \"application_context\" =&gt; &#91;\n    \"return_url\" =&gt; APP_BASE_URL . \"\/public\/thankyou.php\",\n    \"cancel_url\" =&gt; APP_BASE_URL . \"\/public\/cancel.php\"\n  ]\n], $token);\n\nheader(\"Content-Type: application\/json\");\necho json_encode($order ?? &#91;\"error\"=&gt;\"paypal create failed\"]);\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\"><code>public\/paypal_capture.php<\/code><\/h3>\n\n\n\n<p>Captures the PayPal payment after approval:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;?php\nrequire_once __DIR__ . \"\/..\/config.php\";\nrequire_once __DIR__ . \"\/..\/src\/lib\/helpers.php\";\n\n$payload = json_decode(file_get_contents(\"php:\/\/input\"), true);\n$orderID = $payload&#91;\"orderID\"] ?? null;\nif (!$orderID) { http_response_code(400); echo json_encode(&#91;\"error\"=&gt;\"missing orderID\"]); exit; }\n\n$token = paypal_access_token();\n$capture = paypal_request(\"POST\", \"\/v2\/checkout\/orders\/$orderID\/capture\", null, $token);\n\nheader(\"Content-Type: application\/json\");\necho json_encode($capture ?? &#91;\"error\"=&gt;\"paypal capture failed\"]);\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\"><code>public\/place_printful_after_payment.php<\/code><\/h3>\n\n\n\n<p>Once PayPal capture returns <strong>COMPLETED<\/strong>, call this to place the Printful order.<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p>In production, you should <strong>store the item + customer data server-side<\/strong> when you create the PayPal order, then retrieve it here. This demo just shows the call.<\/p>\n<\/blockquote>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;?php\nrequire_once __DIR__ . \"\/..\/config.php\";\nrequire_once __DIR__ . \"\/..\/src\/lib\/helpers.php\";\n\n$payload = json_decode(file_get_contents(\"php:\/\/input\"), true);\n$cap = $payload&#91;\"capture\"] ?? &#91;];\n\n\/\/ In a real app, persist item + customer details before payment and reload them here.\n\/\/ For brevity, this is a placeholder. Replace with your stored checkout data.\n$customer = &#91;\n  \"name\" =&gt; \"PayPal Customer\",\n  \"email\" =&gt; \"customer@example.com\",\n  \"address1\" =&gt; \"Address Line\",\n  \"city\" =&gt; \"City\",\n  \"state_code\" =&gt; \"CA\",\n  \"country_code\" =&gt; \"US\",\n  \"zip\" =&gt; \"94105\"\n];\n$item = &#91;\n  \"title\" =&gt; \"Art Print\",\n  \"image_url\" =&gt; \"https:\/\/via.placeholder.com\/1200x1600.png?text=Art+Print\",\n  \"size\" =&gt; \"18x24\",\n  \"quantity\" =&gt; 1\n];\n\nglobal $SIZE_MAP;\ntry {\n  $placed = place_printful_order($customer, $item, $SIZE_MAP);\n  header(\"Content-Type: application\/json\");\n  echo json_encode(&#91;\"ok\"=&gt;true, \"placed\"=&gt;$placed]);\n} catch (Exception $e) {\n  http_response_code(500);\n  echo json_encode(&#91;\"error\"=&gt;$e-&gt;getMessage()]);\n}\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>11) Thank-you &amp; cancel pages<\/strong><\/h2>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;!-- public\/thankyou.php --&gt;\n&lt;?php ?&gt;&lt;!doctype html&gt;\n&lt;html&gt;&lt;head&gt;&lt;meta charset=\"utf-8\"&gt;&lt;title&gt;Thank you&lt;\/title&gt;&lt;\/head&gt;\n&lt;body&gt;&lt;h1&gt;Thank you!&lt;\/h1&gt;&lt;p&gt;Your payment was received. You will receive updates by email.&lt;\/p&gt;&lt;\/body&gt;&lt;\/html&gt;\n<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;!-- public\/cancel.php --&gt;\n&lt;?php ?&gt;&lt;!doctype html&gt;\n&lt;html&gt;&lt;head&gt;&lt;meta charset=\"utf-8\"&gt;&lt;title&gt;Order canceled&lt;\/title&gt;&lt;\/head&gt;\n&lt;body&gt;&lt;h1&gt;Order canceled&lt;\/h1&gt;&lt;p&gt;Your payment was not completed.&lt;\/p&gt;&lt;\/body&gt;&lt;\/html&gt;\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>12) Local testing<\/strong><\/h2>\n\n\n\n<p>From the project root:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>php -S localhost:8000 -t public\n<\/code><\/pre>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Browse to <code>http:\/\/localhost:8000<\/code><\/li>\n\n\n\n<li>Click a painting \u2192 Order Print \u2192 Checkout<\/li>\n\n\n\n<li>Use <strong>Stripe test cards<\/strong> or <strong>PayPal Sandbox<\/strong><\/li>\n\n\n\n<li>For Stripe webhooks locally, use the Stripe CLI to forward events: <code>stripe listen --forward-to localhost:8000\/public\/stripe_webhook.php<\/code><\/li>\n\n\n\n<li>Replace the <strong><code>$SIZE_MAP<\/code><\/strong> with real variant IDs before live orders.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>13) Production checklist<\/strong><\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Set real keys in <code>config.php<\/code><\/li>\n\n\n\n<li>Use your HTTPS domain in <code>APP_BASE_URL<\/code><\/li>\n\n\n\n<li>Use <strong>live<\/strong> Stripe keys &amp; webhook secret<\/li>\n\n\n\n<li>Use <strong>live<\/strong> PayPal endpoint &amp; credentials<\/li>\n\n\n\n<li>Persist checkout data on your server (especially for PayPal) and load it for the Printful call<\/li>\n\n\n\n<li>Replace placeholder images with your hi-res art URLs<\/li>\n\n\n\n<li>Confirm your Printful billing\/return address, and shipping settings<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>14) FAQ<\/strong><\/h2>\n\n\n\n<p><strong>Do I need to create products in Printful?<\/strong><br>No. With this approach you <strong>send the artwork URL + variant ID at order time<\/strong>. You do need correct <strong>variant IDs<\/strong> for the base product\/sizes you plan to sell.<\/p>\n\n\n\n<p><strong>Where do I get variant IDs?<\/strong><br>From Printful\u2019s product catalog: choose your base product (e.g. poster paper you like), then note the variant IDs for the sizes you want (e.g., 12\u00d718, 18\u00d724). Put them in <code>$SIZE_MAP<\/code>.<\/p>\n\n\n\n<p><strong>Can I customize paper type\/frame?<\/strong><br>Yes \u2014 use a different <code>$SIZE_MAP<\/code> per material (or add a \u201cmaterial\u201d dropdown and map <code>(material,size)<\/code> to the right variant IDs).<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Get the ready-to-run files<\/strong><\/h2>\n\n\n\n<p>You can copy everything above into your project, or just download the prepared starter:<\/p>\n\n\n\n<p><strong>\u27a1\ufe0f <a href=\"https:\/\/www.ultimatewb.com\/docs\/printful-php-starter-kit.zip\">Download the ZIP<\/a><\/strong><\/p>\n\n\n\n<p>Inside you\u2019ll find the exact files shown here, already organized. Open <code>config.php<\/code>, drop in your keys, replace <code>\\$SIZE_MAP<\/code> with your real variant IDs, and you\u2019re good to go.<\/p>\n\n\n\n<p>If you use this tutorial to integrate Printful in your website, let us know how it goes!<\/p>\n\n\n\n<p>If you are looking for a Printful integration in your UltimateWB website and would like this added as a built-in feature, please <a href=\"https:\/\/www.ultimatewb.com\/contactus\">let us know<\/a> and we will review on the demand for it.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p>Are you ready to design &amp; build your own website? Learn more about&nbsp;<a href=\"https:\/\/www.ultimatewb.com\/\">UltimateWB<\/a>! We also offer&nbsp;<a href=\"https:\/\/www.ultimatewb.com\/web-design-packages\">web design packages<\/a>&nbsp;if you would like your website designed and built for you.<\/p>\n\n\n\n<p><em>Got a techy\/website question? Whether it\u2019s about UltimateWB or another website builder, web hosting, or other aspects of websites, just send in your question in the&nbsp;<a href=\"https:\/\/www.ultimatewb.com\/ask-david\">\u201cAsk David!\u201d form<\/a>. We will email you when the answer is posted on the UltimateWB \u201cAsk David!\u201d section.<\/em><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Want to turn your photos, artwork, drawings, or paintings into ready-to-order prints? In this tutorial, I\u2019ll show you how to add a simple \u201cOrder a Print\u201d button to your website, connect it with Printful to handle fulfillment, and accept payments &hellip; <a href=\"https:\/\/www.ultimatewb.com\/blog\/7296\/how-to-add-order-a-print-to-a-custom-website-printful-stripe-paypal-php\/\">Continue reading <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1034,1943,4512],"tags":[5390,5395,5391,5400,5397,5393,5398,5399,5389,5394,5387,5386,5396,5388,5392],"class_list":["post-7296","post","type-post","status-publish","format-standard","hentry","category-ask-david","category-business","category-integration-tutorials","tag-art-print-ecommerce-tutorial","tag-custom-website-prints","tag-order-a-print-button","tag-paypal-api-php","tag-paypal-php-checkout","tag-php-printful-example","tag-php-stripe-paypal-integration","tag-print-on-demand-website","tag-printful-api-php","tag-printful-custom-integration","tag-printful-integration","tag-sell-artwork-online","tag-sell-photos-online-prints","tag-stripe-php-checkout","tag-stripe-webhook-php"],"_links":{"self":[{"href":"https:\/\/www.ultimatewb.com\/blog\/wp-json\/wp\/v2\/posts\/7296"}],"collection":[{"href":"https:\/\/www.ultimatewb.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.ultimatewb.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.ultimatewb.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.ultimatewb.com\/blog\/wp-json\/wp\/v2\/comments?post=7296"}],"version-history":[{"count":3,"href":"https:\/\/www.ultimatewb.com\/blog\/wp-json\/wp\/v2\/posts\/7296\/revisions"}],"predecessor-version":[{"id":7300,"href":"https:\/\/www.ultimatewb.com\/blog\/wp-json\/wp\/v2\/posts\/7296\/revisions\/7300"}],"wp:attachment":[{"href":"https:\/\/www.ultimatewb.com\/blog\/wp-json\/wp\/v2\/media?parent=7296"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.ultimatewb.com\/blog\/wp-json\/wp\/v2\/categories?post=7296"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.ultimatewb.com\/blog\/wp-json\/wp\/v2\/tags?post=7296"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}