@automattic/newspack-blocks 4.28.0 → 4.29.0-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/dist/blocks/checkout-button/block.json +3 -0
  3. package/dist/editor-rtl.css +16 -13
  4. package/dist/editor.asset.php +1 -1
  5. package/dist/editor.css +16 -13
  6. package/dist/editor.js +30 -25
  7. package/dist/modal.asset.php +1 -1
  8. package/dist/modal.js +1 -1
  9. package/dist/modalCheckout-rtl.css +1 -1
  10. package/dist/modalCheckout.asset.php +1 -1
  11. package/dist/modalCheckout.css +1 -1
  12. package/dist/modalCheckout.js +1 -1
  13. package/includes/class-modal-checkout.php +131 -168
  14. package/includes/class-newspack-blocks.php +1 -0
  15. package/includes/modal-checkout/class-checkout-data.php +23 -0
  16. package/newspack-blocks.php +2 -2
  17. package/package.json +5 -5
  18. package/src/blocks/checkout-button/block.json +3 -0
  19. package/src/blocks/checkout-button/coupon-control.js +150 -0
  20. package/src/blocks/checkout-button/edit.js +35 -2
  21. package/src/blocks/checkout-button/edit.scss +3 -2
  22. package/src/blocks/checkout-button/view.php +41 -2
  23. package/src/blocks/donate/frontend/class-newspack-blocks-donate-renderer-frequency-based.php +2 -2
  24. package/src/blocks/donate/frontend/class-newspack-blocks-donate-renderer-tiers-based.php +1 -1
  25. package/src/modal-checkout/checkout-button-trigger.js +91 -6
  26. package/src/modal-checkout/checkout-button-trigger.test.js +143 -1
  27. package/src/modal-checkout/checkout.scss +1 -53
  28. package/src/modal-checkout/index.js +7 -89
  29. package/src/modal-checkout/modal.js +5 -27
  30. package/src/modal-checkout/templates/form-checkout.php +10 -20
  31. package/src/modal-checkout/templates/form-coupon.php +9 -0
  32. package/src/modal-checkout/templates/thankyou.php +1 -1
  33. package/vendor/composer/installed.php +2 -2
  34. package/webpack.config.js +6 -12
@@ -1 +1 @@
1
- (()=>{"use strict";const e=(e,t={})=>({...t,action:e}),t=["action","action_type","amount","currency","product_id","product_type","variation_id","variation_ids","is_variable","is_grouped","child_ids","price_summary","newspack_popup_id","prompt_title","gate_post_id","recurrence","referrer"],o=(e,o="np_modal_checkout_interaction")=>{if("function"==typeof window.gtag&&e){const c={};for(const o of t)e[o]&&("boolean"==typeof e[o]?e[o]=e[o]?"yes":"no":"true"===e[o]?e[o]="yes":"false"===e[o]&&(e[o]="no"),c[o]=e[o].toString());window.gtag("event",o,c)}};function c(e){"undefined"!=typeof document&&"function"==typeof e&&("complete"!==document.readyState&&"interactive"!==document.readyState?document.addEventListener("DOMContentLoaded",e):e())}function n(e){const t="string"==typeof e?document.getElementById(e):e;if(!t)return console.warn("No container found for checkout data"),{};let o={};if("FORM"===t.tagName){const e=new FormData(t);o=Object.fromEntries(e)}const c=t.dataset.checkout;if(!c)return console.warn("No checkout data found"),o;try{o={...o,...JSON.parse(c)}}catch(e){console.warn("Error parsing checkout data")}const n=new URL(t.ownerDocument.defaultView.location.href);return n.searchParams.get("action_type")&&(o.action_type=n.searchParams.get("action_type")),o}const r=(t="continue")=>{if("function"!=typeof window.gtag)return;const{action_type:c,amount:r,currency:i,product_id:a,product_type:s,recurrence:d,referrer:u,variation_id:l="",gate_post_id:p="",newspack_popup_id:_="",prompt_title:h=""}=n("modal-checkout-product-details"),m={action_type:c,amount:r,currency:i,product_id:a,product_type:s,recurrence:d,referrer:u};l&&(m.variation_id=l),p&&(m.gate_post_id=p),_&&(m.newspack_popup_id=_),h&&(m.prompt_title=h);const f=e(t,m);o(f)};var i;i=jQuery,c(()=>{if(!i)return void console.warn("jQuery is not available.");const t=document.querySelector("#newspack_modal_checkout_container"),a=newspackBlocksModalCheckout.newspack_class_prefix,s=new CustomEvent("checkout-ready"),d=new CustomEvent("checkout-complete"),u=new CustomEvent("checkout-cancel"),l=new CustomEvent("checkout-place-order-start"),p=new CustomEvent("checkout-place-order-success"),_=new CustomEvent("checkout-place-order-error"),h=new CustomEvent("checkout-place-order-critical-error");function m(){i(".woocommerce-notices-wrapper").empty(),i(`.woocommerce-NoticeGroup-checkout, .${a}__inline-error, .woocommerce-error, .woocommerce-message, .wc-block-components-notice-banner`).remove()}function f(r=!0){t.checkoutReady=!0,t.dispatchEvent(s),r&&c(function(){if("function"!=typeof window.gtag)return;const t=n("modal-checkout-product-details"),c=e("loaded",t);o(c)})}if(newspackBlocksModalCheckout.is_checkout_complete)(()=>{if("function"!=typeof window.gtag)return;const t=n("modal-checkout-product-details"),c=e("form_submission_success",t);o(c)})(),t&&(t.checkoutComplete=!0,t.dispatchEvent(d));else{function g(){i("#wcpay-express-checkout-button-separator, #wc-stripe-payment-request-button-separator, #wc-stripe-express-checkout-button-separator").after('<div class="newspack-ui__word-divider">'+newspackBlocksModalCheckout.divider_text+"</div>");let c=[];const s=i("form.checkout"),d=i("#checkout_cancel");if(d.length&&d.on("click",function(){t.dispatchEvent(u)}),!s.length)return console.warn("Checkout form is not available"),void f();const g=i("form.modal_checkout_coupon"),w=i("form.modal_checkout_nyp"),v=i("#checkout_continue"),b=i("#customer_details"),y=i("#after_customer_details"),C=i(".newspack-wcsg--wrapper");function x(){const e=i('input[name="payment_method"]:checked').val();i(".wc_payment_method").removeClass("selected"),i(".wc_payment_method.payment_method_"+e).addClass("selected")}i('input[name="payment_method"]').change(x),i(document).on("payment_method_selected",x),i(document).on("updated_checkout",x),x();let E=!1;function M(e,t=!0){const o=i("#modal-checkout-product-details strong");return!(!o.length||!e||(t&&o.html()!==e?o.html(e):t||o.text()===e||o.text(e),0))}s.on("checkout_place_order",function(){E||(E=!0,t.dispatchEvent(l))}),function(e,t){e.checkoutPlaceOrderProcessing?t():e.addEventListener("checkout-place-order-processing",t)}(t,function(){E&&(s.is(".processing")||(E=!1,t.dispatchEvent(_)))}),s.on("checkout_place_order_success",function(){E=!1,t.dispatchEvent(p)}),i(document.body).on("checkout_error",function(e,o){E&&(E=!1,o&&o.indexOf(newspackBlocksModalCheckout.labels.critical_error)>=0?t.dispatchEvent(h):t.dispatchEvent(_))}),s.on("update_checkout",function(){E&&(E=!1,t.dispatchEvent(_))}),i(document).on("updated_checkout",function(){i("#payment .wc_payment_methods").length?i("#after_customer_details > h3").show():i("#after_customer_details > h3").hide()}),i(document).on("updated_checkout",function(){const e=i(".order-review-wrapper");if(!e.length)return;const t=e.find("table");t.is(".empty")?e.addClass("hidden"):(t.unblock(),e.removeClass("hidden"));const o=i("#after_customer_details").hasClass("transaction-details-expanded"),c=i(".payment_methods");if(c.length){const t=e.clone();o&&i('[id="order_review_heading"]',t).attr("aria-expanded","true"),i(".order-review-wrapper").remove(),c.after(t)}else o||e.find("#order_review_heading").trigger("click")});let $=!1;function O(){const e=i("form.checkout");return e.length?e.serialize():""}i(document).on("updated_checkout",function(){!function(){const e=i(".order-review-wrapper .woocommerce-checkout-review-order-table tr.cart_item").first();if(!e.length)return!1;const t=e.find(".product-name").clone();t.find(".product-quantity").remove();const o=t.text().replace(/\s+/g," ").trim(),c=e.find(".product-total").text().replace(/\s+/g," ").trim();return!(!o||!c)&&M(`${o}: ${c}`,!1)}()?function(){$&&$.abort();const e=i.ajax({url:newspackBlocksModalCheckout.ajax_url,method:"POST",data:{action:"get_cart_product_summary",modal_checkout:1,post_data:O()},success:t=>{$===e&&M(t)},complete:()=>{$===e&&($=!1)}});$=e}():$&&($.abort(),$=!1)}),i(document).on("click","#order_review_heading",function(){i(this).attr("aria-expanded",function(e,t){return"false"===t?"true":"false"}),i("#after_customer_details").toggleClass("transaction-details-expanded")});let B=!1;function N(e=function(){return i(".order-review-wrapper tr.order-total:not(.recurring-total) .amount").first().text().replace(/\s+/g," ").trim()}()){let t=newspackBlocksModalCheckout.labels.complete_button;if(t){if(e&&i("#place_order").has(i("span.cart-price"))){const o=i("<div>"+t+"</div>");o.find(".cart-price").html(e),t=o.html()}i("#place_order").html(t),i("#place_order_clone").html(t),e||function(e){B&&B.abort();const t=i.ajax({url:newspackBlocksModalCheckout.ajax_url,method:"POST",data:{action:"get_cart_total",modal_checkout:1,post_data:O()},success:o=>{o&&B===t&&e(o)},complete:()=>{B===t&&(B=!1)}});B=t}(N)}}if(i(document).on("updated_checkout",function(){N()}),C.length){const e=C.find(".newspack-wcsg--gift-toggle input"),t=C.find(".newspack-wcsg--gift-email");e.on("change",function(){e.is(":checked")?t.addClass("visible"):t.removeClass("visible")})}function j(e){s.find(".input-text, select, input:checkbox").trigger("validate").trigger("blur");let t=!1;const o=[],c=e=>{const c=i("#"+e.data("id")+"_field");if(c?.length){t||(t=c);const o=c.find(".woocommerce-error");o.length&&o.remove(),c.addClass("woocommerce-invalid").removeClass("woocommerce-valid"),c.append(`<span class="${a}__inline-error">`+e.text()+"</span>"),e.remove()}else e.is("li")||(e=i("<li />").append(e)),o.push(e)};if(m(),0!==e.trimStart().indexOf("<")?c(i("<li />").append(e)):e.includes("<li")?i(e).find("li").each(function(){c(i(this))}):c(i(e)),o.length){t=!1;const e=i('<div class="woocommerce-NoticeGroup woocommerce-NoticeGroup-checkout"/>').append(i('<ul class="woocommerce-error" role="alert" />').append(o));s.prepend(e),e.get(0).scrollIntoView({behavior:"smooth"})}t?.length&&(window.scroll({top:t.offset().top-100,left:0,behavior:"smooth"}),t.find("input.input-text, select, input:checkbox").trigger("focus")),T(s),i(document.body).trigger("update_checkout"),i(document.body).trigger("checkout_error",[e])}function P(e){e.preventDefault(),e.stopImmediatePropagation(),L(),r("continue")}function S(e){const t=window.newspack_grecaptcha||{};if(m(),i("#checkout_details").remove(),e)s.attr("data-skip-recaptcha","1"),s.append('<input name="is_validation_only" type="hidden" value="1" />'),"v3"===t?.version&&t.destroy(s.get()),g.length&&g.hide(),w.length&&w.hide(),b.show(),y.hide(),b.find("input").first().focus(),c=function(e,t){const o=i._data(e,"events");return o?t?o[t]||[]:o:[]}(s[0],"submit").slice(0),c.forEach(e=>{s.off("submit",e.handler)}),s.on("submit",P);else{s.removeAttr("data-skip-recaptcha");const e=s.find('[name="is_validation_only"]');if(e.length&&e.remove(),t?.render){s.data("newspack-recaptcha","newspack_modal_checkout");const e=()=>{m(),s.get(0).scrollIntoView({behavior:"smooth"})},o=e=>j(e);t.render(s.get(),e,o),i(document).on("updated_checkout",()=>t.render(s.get(),e,o)),i(document.body).on("checkout_error",()=>t.render(s.get(),e,o))}g.length&&g.show(),w.length&&w.show(),b.hide(),y.show(),function(){i("#checkout_details").remove();const e={};s.serializeArray().forEach(t=>{e[t.name]=t.value});const t=`${newspackBlocksModalCheckout.newspack_class_prefix}__font--xs`,o=[];o.push('<div class="billing-details">'),o.push("<h3>"+newspackBlocksModalCheckout.labels.billing_details+"</h3>"),(e.billing_first_name||e.billing_last_name)&&o.push(`<p class="${t}">`+e.billing_first_name+" "+e.billing_last_name+"</p>"),e.billing_company&&o.push(`<p class="${t}">`+e.billing_company+"</p>");let c="";if((e.billing_address_1||e.billing_address_2)&&(c=`<p class="${t}">`,e.billing_address_1&&(c+=e.billing_address_1),e.billing_address_2&&(c+=" "+e.billing_address_2),c+="<br>",e.billing_city&&(c+=e.billing_city),e.billing_state&&(c+=", "+e.billing_state),e.billing_postcode&&(c+=" "+e.billing_postcode),c+="<br>",e.billing_country&&(c+=e.billing_country)),o.push(c),e.billing_email&&o.push(`<p class="${t}">`+e.billing_email+"</p>"),o.push("</div>"),e.hasOwnProperty("shipping_address_1")){o.push('<div class="shipping-details">'),o.push("<h3>"+newspackBlocksModalCheckout.labels.shipping_details+"</h3>");let n="";e.ship_to_different_address?(n=`<p class="${t}">`,e.shipping_address_1&&(n+=e.shipping_address_1),e.shipping_address_2&&(n+=" "+e.shipping_address_2),n+="<br>",e.shipping_city&&(n+=e.shipping_city),e.shipping_state&&(n+=", "+e.shipping_state),e.shipping_postcode&&(n+=" "+e.shipping_postcode),n+="<br>",e.shipping_country&&(n+=e.shipping_country)):n=c,o.push(n),o.push("</div>")}e.hasOwnProperty("newspack_wcsg_is_gift")&&e.hasOwnProperty("wcsg_gift_recipients_email")&&e.newspack_wcsg_is_gift&&e.wcsg_gift_recipients_email&&(o.push('<div class="gift-details">'),o.push("<h3>"+newspackBlocksModalCheckout.labels.gift_recipient+"</h3>"),o.push(`<p class="${t}">`+e.wcsg_gift_recipients_email+"</p>")),i(".order-details-summary").after('<div id="checkout_details">'+o.join("")+"</div>")}(),s.off("submit",P),c.forEach(e=>{s.on("submit",e.handler)}),k(),i(document.body).trigger("update_checkout",{update_shipping_method:!1})}s.triggerHandler("editing_details",[e]),window.scroll({top:0,left:0,behavior:"smooth"})}function L(e=!1,t=()=>{}){if(!D(s))return console.warn("Unable to block the form"),t(),!1;m();const o=s.find(".woocommerce-NoticeGroup.woocommerce-NoticeGroup-checkout");o.length&&o.remove();const c=["save_user_in_woopay"],n=s.serializeArray().filter(e=>!c.includes(e.name));n.push({name:"newspack_blocks_checkout_action",value:"1"}),i.ajax({type:"POST",url:wc_checkout_params.checkout_url,data:n,dataType:"html",success:o=>{let c;try{c=JSON.parse(o)}catch(e){c={messages:'<div class="woocommerce-error">'+wc_checkout_params.i18n_checkout_error+"</div>"}}e||!0!==c.reload?(T(s),c.messages?e||(c.messages?j(c.messages):j(`<div class="${a}__inline-error">`+wc_checkout_params.i18n_checkout_error+"</div>")):(S(!1),i._data(s[0],"events")?.click?.some(e=>"#checkout_edit_billing"===e.selector)||s.on("click","#checkout_edit_billing",function(e){e.preventDefault(),S(!0),r("back")})),t(c)):window.location.reload()},error:(o,c,n)=>{let r="";e||(r='<div class="woocommerce-error">'+(n||wc_checkout_params.i18n_checkout_error)+"</div>",j(r)),t({messages:r})}})}function D(e){return!e.is(".modal-processing")&&(e.find("button[type=submit]").each((e,t)=>{i(t).attr("disabled",!0)}),e.addClass("modal-processing"),!0)}function T(e){return!!e.is(".modal-processing")&&(e.find("button[type=submit]").each((e,t)=>{i(t).attr("disabled",!1)}),e.removeClass("modal-processing"),!0)}v.length?(S(!0),C.length?f():L(!0,f)):f(),g.length&&(g.on("submit",function(e){if(e.preventDefault(),!D(g))return!1;const t={security:wc_checkout_params.apply_coupon_nonce,coupon_code:g.find('input[name="coupon_code"]').val()};i.ajax({type:"POST",url:wc_checkout_params.wc_ajax_url.toString().replace("%%endpoint%%","apply_coupon"),data:t,dataType:"html",success:e=>{if(m(),g.find(".result").remove(),e){const o=e.includes("error");g.append(`<p class="result ${a}__helper-text ${o?a+"__inline-error":""}">`+i(e).text()+"</p>"),o?(g.find('input[name="coupon_code"]').focus(),g.find('h3, input[name="coupon_code"]').addClass("newspack-ui__field-error")):(g.find('input[name="coupon_code"]').focus(),g.find('h3, input[name="coupon_code"]').removeClass("newspack-ui__field-error")),i(document.body).trigger("applied_coupon_in_checkout",[t.coupon_code]),i(document.body).trigger("update_checkout",{update_shipping_method:!1})}},complete:()=>{T(g)}})}),i(document.body).on("removed_coupon_in_checkout",()=>{m(),g.find(".result").remove(),g.find('input[name="coupon_code"]').val("").focus()})),w.length&&w.on("submit",function(e){if(e.preventDefault(),!D(w))return!1;const t=w.find('input[name="price"]');t.attr("disabled",!0);const o={_ajax_nonce:newspackBlocksModalCheckout.nyp_nonce,action:"process_name_your_price_request",price:w.find('input[name="price"]').val(),product_id:w.find('input[name="product_id"]').val(),newspack_checkout_name_your_price:w.find('input[name="newspack_checkout_name_your_price"]').val()};i.ajax({type:"POST",url:newspackBlocksModalCheckout.ajax_url,data:o,success:({success:e,data:t})=>{m(),w.find(".result").remove(),w.append(`<p class="result ${a}__helper-text ${e?"":a+"__inline-error"}">`+t.message+"</p>"),e?w.find('h3, input[name="price"]').removeClass("newspack-ui__field-error"):(w.find('input[name="price"]').focus(),w.find('h3, input[name="price"]').addClass("newspack-ui__field-error")),i(document.body).trigger("update_checkout",{update_shipping_method:!1})},complete:()=>{T(w),t.attr("disabled",!1),t.focus()}})}),s.on("click","#place_order",function(){(()=>{if("function"!=typeof window.gtag)return;const t=n("modal-checkout-product-details"),c=e("form_submission",t);o(c)})()});const G=function(){if(!t)return;const e="data-newspack-error-handled",o=o=>{o.hasAttribute(e)||(o.setAttribute(e,"true"),i(o).addClass(`${a}__notice ${a}__notice--error`),t.dispatchEvent(_),o.scrollIntoView({behavior:"smooth",block:"center"}))},c=e=>!!e.classList?.contains("woocommerce-error")||!(!e.classList?.contains("wc-block-components-notice-banner")||!e.classList?.contains("is-error")),n=new MutationObserver(e=>{for(const t of e)for(const e of t.addedNodes){if(e.nodeType!==Node.ELEMENT_NODE)continue;if(c(e))return void o(e);const t=e.querySelector?.(".woocommerce-error, .wc-block-components-notice-banner.is-error");if(t)return void o(t)}});return n.observe(t,{childList:!0,subtree:!0}),n}();if(G){const e=()=>G.disconnect();t.addEventListener("checkout-complete",e),t.addEventListener("checkout-cancel",e),window.addEventListener("beforeunload",e)}}g()}function k(){const e=i("#newspack_subscription_terms_confirmation, #newspack_subscription_confirmation");if(e.length){const t=i("form.checkout"),o=t.find("button#place_order, button#place_order_clone");function c(){const t=e.is(":checked");o.each((e,o)=>{i(o).attr("disabled",!t)})}c(),e.on("change",c),t.on("submit",function(t){if(!e.is(":checked"))return t.preventDefault(),t.stopPropagation(),!1})}}i(document.body).on("updated_checkout payment_method_selected checkout_error",k),i(document).ready(k),i(document.body).on("checkout_error",function(){const e=i(".woocommerce-NoticeGroup-checkout, .woocommerce-notices-wrapper:not(:empty)");e.length&&e.each((e,t)=>i(t).addClass(`${a}__notice ${a}__notice--error`));const t=i("#checkout_error_back");t.length&&t.on("click",e=>{e.preventDefault(),parent.newspackCloseModalCheckout()}),f(!1)}),document.addEventListener("keydown",function(e){"Escape"===e.key&&parent.newspackCloseModalCheckout()}),newspackBlocksModalCheckout.is_error&&i(document.body).trigger("checkout_error")})})();
1
+ (()=>{"use strict";const e=(e,t={})=>({...t,action:e}),t=["action","action_type","amount","currency","product_id","product_type","variation_id","variation_ids","is_variable","is_grouped","child_ids","price_summary","newspack_popup_id","prompt_title","gate_post_id","recurrence","referrer"],o=(e,o="np_modal_checkout_interaction")=>{if("function"==typeof window.gtag&&e){const c={};for(const o of t)e[o]&&("boolean"==typeof e[o]?e[o]=e[o]?"yes":"no":"true"===e[o]?e[o]="yes":"false"===e[o]&&(e[o]="no"),c[o]=e[o].toString());window.gtag("event",o,c)}};function c(e){"undefined"!=typeof document&&"function"==typeof e&&("complete"!==document.readyState&&"interactive"!==document.readyState?document.addEventListener("DOMContentLoaded",e):e())}function n(e){const t="string"==typeof e?document.getElementById(e):e;if(!t)return console.warn("No container found for checkout data"),{};let o={};if("FORM"===t.tagName){const e=new FormData(t);o=Object.fromEntries(e)}const c=t.dataset.checkout;if(!c)return console.warn("No checkout data found"),o;try{o={...o,...JSON.parse(c)}}catch(e){console.warn("Error parsing checkout data")}const n=new URL(t.ownerDocument.defaultView.location.href);return n.searchParams.get("action_type")&&(o.action_type=n.searchParams.get("action_type")),o}const i=(t="continue")=>{if("function"!=typeof window.gtag)return;const{action_type:c,amount:i,currency:r,product_id:s,product_type:a,recurrence:d,referrer:u,variation_id:p="",gate_post_id:l="",newspack_popup_id:_="",prompt_title:h=""}=n("modal-checkout-product-details"),m={action_type:c,amount:i,currency:r,product_id:s,product_type:a,recurrence:d,referrer:u};p&&(m.variation_id=p),l&&(m.gate_post_id=l),_&&(m.newspack_popup_id=_),h&&(m.prompt_title=h);const f=e(t,m);o(f)};var r;r=jQuery,c(()=>{if(!r)return void console.warn("jQuery is not available.");const t=document.querySelector("#newspack_modal_checkout_container"),s=newspackBlocksModalCheckout.newspack_class_prefix,a=new CustomEvent("checkout-ready"),d=new CustomEvent("checkout-complete"),u=new CustomEvent("checkout-cancel"),p=new CustomEvent("checkout-place-order-start"),l=new CustomEvent("checkout-place-order-success"),_=new CustomEvent("checkout-place-order-error"),h=new CustomEvent("checkout-place-order-critical-error");function m(){r(".woocommerce-notices-wrapper").empty(),r(`.woocommerce-NoticeGroup-checkout, .${s}__inline-error, .woocommerce-error, .woocommerce-message, .wc-block-components-notice-banner`).remove()}function f(i=!0){t.checkoutReady=!0,t.dispatchEvent(a),i&&c(function(){if("function"!=typeof window.gtag)return;const t=n("modal-checkout-product-details"),c=e("loaded",t);o(c)})}if(newspackBlocksModalCheckout.is_checkout_complete)(()=>{if("function"!=typeof window.gtag)return;const t=n("modal-checkout-product-details"),c=e("form_submission_success",t);o(c)})(),t&&(t.checkoutComplete=!0,t.dispatchEvent(d));else{function g(){r("#wcpay-express-checkout-button-separator, #wc-stripe-payment-request-button-separator, #wc-stripe-express-checkout-button-separator").after('<div class="newspack-ui__word-divider">'+newspackBlocksModalCheckout.divider_text+"</div>");let c=[];const a=r("form.checkout"),d=r("#checkout_cancel");if(d.length&&d.on("click",function(){t.dispatchEvent(u)}),!a.length)return console.warn("Checkout form is not available"),void f();const g=r("form.modal_checkout_coupon"),w=r("form.modal_checkout_nyp"),v=r("#checkout_continue"),b=r("#customer_details"),y=r("#after_customer_details"),C=r(".newspack-wcsg--wrapper");function E(){const e=r('input[name="payment_method"]:checked').val();r(".wc_payment_method").removeClass("selected"),r(".wc_payment_method.payment_method_"+e).addClass("selected")}r('input[name="payment_method"]').change(E),r(document).on("payment_method_selected",E),r(document).on("updated_checkout",E),E();let x=!1;function M(){const e=r("form.checkout");return e.length?e.serialize():""}a.on("checkout_place_order",function(){x||(x=!0,t.dispatchEvent(p))}),function(e,t){e.checkoutPlaceOrderProcessing?t():e.addEventListener("checkout-place-order-processing",t)}(t,function(){x&&(a.is(".processing")||(x=!1,t.dispatchEvent(_)))}),a.on("checkout_place_order_success",function(){x=!1,t.dispatchEvent(l)}),r(document.body).on("checkout_error",function(e,o){x&&(x=!1,o&&o.indexOf(newspackBlocksModalCheckout.labels.critical_error)>=0?t.dispatchEvent(h):t.dispatchEvent(_))}),a.on("update_checkout",function(){x&&(x=!1,t.dispatchEvent(_))}),r(document).on("updated_checkout",function(){r("#payment .wc_payment_methods").length?r("#after_customer_details > h3").show():r("#after_customer_details > h3").hide()}),r(document).on("updated_checkout",function(){const e=r(".order-review-wrapper");if(!e.length)return;const t=e.find("table");t.is(".empty")?e.addClass("hidden"):(t.unblock(),e.removeClass("hidden"));const o=r(".payment_methods");if(o.length){const t=e.clone();r(".order-review-wrapper").remove(),o.after(t)}});let $=!1;function O(e=function(){return r(".order-review-wrapper tr.order-total:not(.recurring-total) .amount").first().text().replace(/\s+/g," ").trim()}()){let t=newspackBlocksModalCheckout.labels.complete_button;if(t){if(e&&r("#place_order").has(r("span.cart-price"))){const o=r("<div>"+t+"</div>");o.find(".cart-price").html(e),t=o.html()}r("#place_order").html(t),r("#place_order_clone").html(t),e||function(e){$&&$.abort();const t=r.ajax({url:newspackBlocksModalCheckout.ajax_url,method:"POST",data:{action:"get_cart_total",modal_checkout:1,post_data:M()},success:o=>{o&&$===t&&e(o)},complete:()=>{$===t&&($=!1)}});$=t}(O)}}if(r(document).on("updated_checkout",function(){O()}),C.length){const e=C.find(".newspack-wcsg--gift-toggle input"),t=C.find(".newspack-wcsg--gift-email");e.on("change",function(){e.is(":checked")?t.addClass("visible"):t.removeClass("visible")})}function N(e){a.find(".input-text, select, input:checkbox").trigger("validate").trigger("blur");let t=!1;const o=[],c=e=>{const c=r("#"+e.data("id")+"_field");if(c?.length){t||(t=c);const o=c.find(".woocommerce-error");o.length&&o.remove(),c.addClass("woocommerce-invalid").removeClass("woocommerce-valid"),c.append(`<span class="${s}__inline-error">`+e.text()+"</span>"),e.remove()}else e.is("li")||(e=r("<li />").append(e)),o.push(e)};if(m(),0!==e.trimStart().indexOf("<")?c(r("<li />").append(e)):e.includes("<li")?r(e).find("li").each(function(){c(r(this))}):c(r(e)),o.length){t=!1;const e=r('<div class="woocommerce-NoticeGroup woocommerce-NoticeGroup-checkout"/>').append(r('<ul class="woocommerce-error" role="alert" />').append(o));a.prepend(e),e.get(0).scrollIntoView({behavior:"smooth"})}t?.length&&(window.scroll({top:t.offset().top-100,left:0,behavior:"smooth"}),t.find("input.input-text, select, input:checkbox").trigger("focus")),j(a),r(document.body).trigger("update_checkout"),r(document.body).trigger("checkout_error",[e])}function B(e){e.preventDefault(),e.stopImmediatePropagation(),P(),i("continue")}function L(e){const t=window.newspack_grecaptcha||{};if(m(),r("#checkout_details").remove(),e)a.attr("data-skip-recaptcha","1"),a.append('<input name="is_validation_only" type="hidden" value="1" />'),"v3"===t?.version&&t.destroy(a.get()),g.length&&g.hide(),w.length&&w.hide(),b.show(),y.hide(),b.find("input").first().focus(),c=function(e,t){const o=r._data(e,"events");return o?t?o[t]||[]:o:[]}(a[0],"submit").slice(0),c.forEach(e=>{a.off("submit",e.handler)}),a.on("submit",B);else{a.removeAttr("data-skip-recaptcha");const e=a.find('[name="is_validation_only"]');if(e.length&&e.remove(),t?.render){a.data("newspack-recaptcha","newspack_modal_checkout");const e=()=>{m(),a.get(0).scrollIntoView({behavior:"smooth"})},o=e=>N(e);t.render(a.get(),e,o),r(document).on("updated_checkout",()=>t.render(a.get(),e,o)),r(document.body).on("checkout_error",()=>t.render(a.get(),e,o))}g.length&&g.show(),w.length&&w.show(),b.hide(),y.show(),function(){r("#checkout_details").remove();const e={};a.serializeArray().forEach(t=>{e[t.name]=t.value});const t=`${newspackBlocksModalCheckout.newspack_class_prefix}__font--xs`,o=[];o.push('<div class="billing-details">'),o.push("<h3>"+newspackBlocksModalCheckout.labels.billing_details+"</h3>"),(e.billing_first_name||e.billing_last_name)&&o.push(`<p class="${t}">`+e.billing_first_name+" "+e.billing_last_name+"</p>"),e.billing_company&&o.push(`<p class="${t}">`+e.billing_company+"</p>");let c="";if((e.billing_address_1||e.billing_address_2)&&(c=`<p class="${t}">`,e.billing_address_1&&(c+=e.billing_address_1),e.billing_address_2&&(c+=" "+e.billing_address_2),c+="<br>",e.billing_city&&(c+=e.billing_city),e.billing_state&&(c+=", "+e.billing_state),e.billing_postcode&&(c+=" "+e.billing_postcode),c+="<br>",e.billing_country&&(c+=e.billing_country)),o.push(c),e.billing_email&&o.push(`<p class="${t}">`+e.billing_email+"</p>"),o.push("</div>"),e.hasOwnProperty("shipping_address_1")){o.push('<div class="shipping-details">'),o.push("<h3>"+newspackBlocksModalCheckout.labels.shipping_details+"</h3>");let n="";e.ship_to_different_address?(n=`<p class="${t}">`,e.shipping_address_1&&(n+=e.shipping_address_1),e.shipping_address_2&&(n+=" "+e.shipping_address_2),n+="<br>",e.shipping_city&&(n+=e.shipping_city),e.shipping_state&&(n+=", "+e.shipping_state),e.shipping_postcode&&(n+=" "+e.shipping_postcode),n+="<br>",e.shipping_country&&(n+=e.shipping_country)):n=c,o.push(n),o.push("</div>")}e.hasOwnProperty("newspack_wcsg_is_gift")&&e.hasOwnProperty("wcsg_gift_recipients_email")&&e.newspack_wcsg_is_gift&&e.wcsg_gift_recipients_email&&(o.push('<div class="gift-details">'),o.push("<h3>"+newspackBlocksModalCheckout.labels.gift_recipient+"</h3>"),o.push(`<p class="${t}">`+e.wcsg_gift_recipients_email+"</p>")),(r("#modal-checkout-product-details").length?r("#modal-checkout-product-details"):r("#after_customer_details")).after('<div id="checkout_details">'+o.join("")+"</div>")}(),a.off("submit",B),c.forEach(e=>{a.on("submit",e.handler)}),k(),r(document.body).trigger("update_checkout",{update_shipping_method:!1})}a.triggerHandler("editing_details",[e]),window.scroll({top:0,left:0,behavior:"smooth"})}function P(e=!1,t=()=>{}){if(!S(a))return console.warn("Unable to block the form"),t(),!1;m();const o=a.find(".woocommerce-NoticeGroup.woocommerce-NoticeGroup-checkout");o.length&&o.remove();const c=["save_user_in_woopay"],n=a.serializeArray().filter(e=>!c.includes(e.name));n.push({name:"newspack_blocks_checkout_action",value:"1"}),r.ajax({type:"POST",url:wc_checkout_params.checkout_url,data:n,dataType:"html",success:o=>{let c;try{c=JSON.parse(o)}catch(e){c={messages:'<div class="woocommerce-error">'+wc_checkout_params.i18n_checkout_error+"</div>"}}e||!0!==c.reload?(j(a),c.messages?e||(c.messages?N(c.messages):N(`<div class="${s}__inline-error">`+wc_checkout_params.i18n_checkout_error+"</div>")):(L(!1),r._data(a[0],"events")?.click?.some(e=>"#checkout_edit_billing"===e.selector)||a.on("click","#checkout_edit_billing",function(e){e.preventDefault(),L(!0),i("back")})),t(c)):window.location.reload()},error:(o,c,n)=>{let i="";e||(i='<div class="woocommerce-error">'+(n||wc_checkout_params.i18n_checkout_error)+"</div>",N(i)),t({messages:i})}})}function S(e){return!e.is(".modal-processing")&&(e.find("button[type=submit]").each((e,t)=>{r(t).attr("disabled",!0)}),e.addClass("modal-processing"),!0)}function j(e){return!!e.is(".modal-processing")&&(e.find("button[type=submit]").each((e,t)=>{r(t).attr("disabled",!1)}),e.removeClass("modal-processing"),!0)}v.length?(L(!0),C.length?f():P(!0,f)):f(),g.length&&(g.on("submit",function(e){if(e.preventDefault(),!S(g))return!1;const t={security:wc_checkout_params.apply_coupon_nonce,coupon_code:g.find('input[name="coupon_code"]').val()};r.ajax({type:"POST",url:wc_checkout_params.wc_ajax_url.toString().replace("%%endpoint%%","apply_coupon"),data:t,dataType:"html",success:e=>{if(m(),g.find(".result").remove(),e){const o=e.includes("error");g.append(`<p class="result ${s}__helper-text ${o?s+"__inline-error":""}">`+r(e).text()+"</p>"),o?(g.find('input[name="coupon_code"]').focus(),g.find('h3, input[name="coupon_code"]').addClass("newspack-ui__field-error")):(g.find('input[name="coupon_code"]').focus(),g.find('h3, input[name="coupon_code"]').removeClass("newspack-ui__field-error")),r(document.body).trigger("applied_coupon_in_checkout",[t.coupon_code]),r(document.body).trigger("update_checkout",{update_shipping_method:!1})}},complete:()=>{j(g)}})}),r(document.body).on("removed_coupon_in_checkout",()=>{m(),g.find(".result").remove(),g.find('input[name="coupon_code"]').val("").focus()})),w.length&&w.on("submit",function(e){if(e.preventDefault(),!S(w))return!1;const t=w.find('input[name="price"]');t.attr("disabled",!0);const o={_ajax_nonce:newspackBlocksModalCheckout.nyp_nonce,action:"process_name_your_price_request",price:w.find('input[name="price"]').val(),product_id:w.find('input[name="product_id"]').val(),newspack_checkout_name_your_price:w.find('input[name="newspack_checkout_name_your_price"]').val()};r.ajax({type:"POST",url:newspackBlocksModalCheckout.ajax_url,data:o,success:({success:e,data:t})=>{m(),w.find(".result").remove(),w.append(`<p class="result ${s}__helper-text ${e?"":s+"__inline-error"}">`+t.message+"</p>"),e?w.find('h3, input[name="price"]').removeClass("newspack-ui__field-error"):(w.find('input[name="price"]').focus(),w.find('h3, input[name="price"]').addClass("newspack-ui__field-error")),r(document.body).trigger("update_checkout",{update_shipping_method:!1})},complete:()=>{j(w),t.attr("disabled",!1),t.focus()}})}),a.on("click","#place_order",function(){(()=>{if("function"!=typeof window.gtag)return;const t=n("modal-checkout-product-details"),c=e("form_submission",t);o(c)})()});const D=function(){if(!t)return;const e="data-newspack-error-handled",o=o=>{o.hasAttribute(e)||(o.setAttribute(e,"true"),r(o).addClass(`${s}__notice ${s}__notice--error`),t.dispatchEvent(_),o.scrollIntoView({behavior:"smooth",block:"center"}))},c=e=>!!e.classList?.contains("woocommerce-error")||!(!e.classList?.contains("wc-block-components-notice-banner")||!e.classList?.contains("is-error")),n=new MutationObserver(e=>{for(const t of e)for(const e of t.addedNodes){if(e.nodeType!==Node.ELEMENT_NODE)continue;if(c(e))return void o(e);const t=e.querySelector?.(".woocommerce-error, .wc-block-components-notice-banner.is-error");if(t)return void o(t)}});return n.observe(t,{childList:!0,subtree:!0}),n}();if(D){const e=()=>D.disconnect();t.addEventListener("checkout-complete",e),t.addEventListener("checkout-cancel",e),window.addEventListener("beforeunload",e)}}g()}function k(){const e=r("#newspack_subscription_terms_confirmation, #newspack_subscription_confirmation");if(e.length){const t=r("form.checkout"),o=t.find("button#place_order, button#place_order_clone");function c(){const t=e.is(":checked");o.each((e,o)=>{r(o).attr("disabled",!t)})}c(),e.on("change",c),t.on("submit",function(t){if(!e.is(":checked"))return t.preventDefault(),t.stopPropagation(),!1})}}r(document.body).on("updated_checkout payment_method_selected checkout_error",k),r(document).ready(k),r(document.body).on("checkout_error",function(){const e=r(".woocommerce-NoticeGroup-checkout, .woocommerce-notices-wrapper:not(:empty)");e.length&&e.each((e,t)=>r(t).addClass(`${s}__notice ${s}__notice--error`));const t=r("#checkout_error_back");t.length&&t.on("click",e=>{e.preventDefault(),parent.newspackCloseModalCheckout()}),f(!1)}),document.addEventListener("keydown",function(e){"Escape"===e.key&&parent.newspackCloseModalCheckout()}),newspackBlocksModalCheckout.is_error&&r(document.body).trigger("checkout_error")})})();
@@ -29,6 +29,14 @@ final class Modal_Checkout {
29
29
  */
30
30
  const CHECKOUT_REGISTRATION_ORDER_META_KEY = '_newspack_checkout_registration_meta';
31
31
 
32
+ /**
33
+ * Session key tracking a coupon auto-applied from a Checkout Button block,
34
+ * used to hide the in-modal coupon form for that coupon.
35
+ *
36
+ * @var string
37
+ */
38
+ const AUTO_APPLIED_COUPON_SESSION_KEY = 'newspack_blocks_auto_applied_coupon';
39
+
32
40
  /**
33
41
  * Billing fields with server-side format validation in the Store API,
34
42
  * mapped from their classic checkout keys to Store API address keys.
@@ -218,8 +226,6 @@ final class Modal_Checkout {
218
226
  // Make the current cart price available to the JavaScript.
219
227
  add_action( 'wp_ajax_get_cart_total', [ __CLASS__, 'get_cart_total_js' ] );
220
228
  add_action( 'wp_ajax_nopriv_get_cart_total', [ __CLASS__, 'get_cart_total_js' ] );
221
- add_action( 'wp_ajax_get_cart_product_summary', [ __CLASS__, 'get_cart_product_summary_js' ] );
222
- add_action( 'wp_ajax_nopriv_get_cart_product_summary', [ __CLASS__, 'get_cart_product_summary_js' ] );
223
229
 
224
230
  // Wrap required checkbox text in a span so it works nicely with the Newspack UI grid layout.
225
231
  add_filter( 'woocommerce_form_field_checkbox', [ __CLASS__, 'wrap_required_checkbox_text' ], 10, 4 );
@@ -449,7 +455,14 @@ final class Modal_Checkout {
449
455
  $cart_item_data = apply_filters( 'newspack_blocks_modal_checkout_cart_item_data', $cart_item_data );
450
456
 
451
457
  \WC()->cart->empty_cart();
452
- \WC()->cart->add_to_cart( $product_id, 1, 0, [], $cart_item_data );
458
+ $cart_item_key = \WC()->cart->add_to_cart( $product_id, 1, 0, [], $cart_item_data );
459
+
460
+ // Auto-apply a coupon attached to the Checkout Button block, if present and
461
+ // valid. Read with a sanitizing filter (satisfies input-sanitization
462
+ // checks); the validate-and-apply gate lives in maybe_auto_apply_coupon()
463
+ // so it can be unit-tested in isolation.
464
+ $coupon_code = (string) filter_input( INPUT_GET, 'coupon', FILTER_SANITIZE_FULL_SPECIAL_CHARS );
465
+ self::maybe_auto_apply_coupon( $coupon_code );
453
466
 
454
467
  // Set checkout registration flag if user is logged not logged in.
455
468
  if ( ! is_user_logged_in() ) {
@@ -491,13 +504,31 @@ final class Modal_Checkout {
491
504
  'referrer' => $referer,
492
505
  ];
493
506
 
494
- /**
495
- * Action to fire for checkout button block modal.
496
- */
497
- \do_action( 'newspack_blocks_checkout_button_modal', $checkout_button_metadata );
507
+ if ( $cart_item_key ) {
508
+ /**
509
+ * Action to fire for checkout button block modal.
510
+ *
511
+ * Fires only when the product actually entered the cart, so consumers
512
+ * recording checkout-initiated events stay in step with adds rejected
513
+ * by an add-to-cart guard.
514
+ */
515
+ \do_action( 'newspack_blocks_checkout_button_modal', $checkout_button_metadata );
516
+ }
498
517
 
499
518
  $checkout_url = apply_filters( 'newspack_blocks_checkout_url', $checkout_url );
500
519
 
520
+ // If the product could not be added (e.g. rejected by an add-to-cart guard),
521
+ // the cart is empty and the checkout would render blank: point the modal at
522
+ // its error screen, which prints the queued error notice and a back button.
523
+ // Like the success path above, an unsupported payment gateway means a
524
+ // full-page rather than iframed checkout, so modal_checkout is omitted.
525
+ if ( ! $cart_item_key ) {
526
+ $checkout_url = \wc_get_cart_url();
527
+ if ( ! self::has_unsupported_payment_gateway() ) {
528
+ $checkout_url = add_query_arg( 'modal_checkout', 1, $checkout_url );
529
+ }
530
+ }
531
+
501
532
  if ( defined( 'DOING_AJAX' ) ) {
502
533
  echo wp_json_encode( [ 'url' => $checkout_url ] );
503
534
  exit;
@@ -685,7 +716,26 @@ final class Modal_Checkout {
685
716
  $coupons = \WC()->cart->get_applied_coupons();
686
717
 
687
718
  \WC()->cart->empty_cart();
688
- \WC()->cart->add_to_cart( $product_id, 1, 0, [], $cart_item_data );
719
+ $cart_item_key = \WC()->cart->add_to_cart( $product_id, 1, 0, [], $cart_item_data );
720
+
721
+ // A rejected add (e.g. an add-to-cart guard) must not report success: surface
722
+ // the error notice the cart queued for it instead of a thank-you message.
723
+ // The consumer appends this message as an HTML string, and unlike template-
724
+ // rendered notices it never passes through kses on output — so filter any
725
+ // third-party notice content here.
726
+ if ( ! $cart_item_key ) {
727
+ $error_notices = \wc_get_notices( 'error' );
728
+ \wc_clear_notices();
729
+ wp_send_json_error(
730
+ [
731
+ 'message' => ! empty( $error_notices[0]['notice'] )
732
+ ? wp_kses_post( $error_notices[0]['notice'] )
733
+ : __( 'This product could not be added to the cart.', 'newspack-blocks' ),
734
+ ]
735
+ );
736
+
737
+ wp_die();
738
+ }
689
739
 
690
740
  if ( ! empty( $coupons ) ) {
691
741
  foreach ( $coupons as $coupon ) {
@@ -1666,61 +1716,79 @@ final class Modal_Checkout {
1666
1716
  }
1667
1717
 
1668
1718
  /**
1669
- * Whether to show order details table.
1719
+ * Auto-apply a coupon attached to a Checkout Button block to the current
1720
+ * cart, gated on validation.
1670
1721
  *
1671
- * @return bool
1722
+ * The reader never typed the code, so anything that would surface it is kept
1723
+ * silent: an invalid, expired, restricted or usage-capped coupon is skipped
1724
+ * with no error notice (validation runs before apply), and the "applied
1725
+ * successfully" success notice that WC_Cart::apply_coupon() queues is
1726
+ * cleared. A successful application is tracked in the session so the modal
1727
+ * can hide its coupon form for that coupon (see should_hide_coupon_form());
1728
+ * any prior marker is reset first.
1729
+ *
1730
+ * @param string $coupon_code Raw coupon code from the request (already
1731
+ * sanitized by the caller, e.g. via filter_input()).
1732
+ *
1733
+ * @return void
1672
1734
  */
1673
- public static function should_show_order_details() {
1674
- $cart = \WC()->cart;
1675
- if ( $cart->is_empty() ) {
1676
- return false;
1677
- }
1678
- if ( ! empty( $cart->get_applied_coupons() ) ) {
1679
- return true;
1680
- }
1681
- if ( \wc_tax_enabled() && ! $cart->display_prices_including_tax() ) {
1682
- return true;
1683
- }
1684
- if ( 1 < $cart->get_cart_contents_count() ) {
1685
- return true;
1735
+ public static function maybe_auto_apply_coupon( $coupon_code ) {
1736
+ if ( ! function_exists( 'WC' ) || ! \WC()->cart ) {
1737
+ return;
1686
1738
  }
1687
- if ( ! empty( $cart->get_fees() ) ) {
1688
- return true;
1739
+ if ( \WC()->session ) {
1740
+ \WC()->session->set( self::AUTO_APPLIED_COUPON_SESSION_KEY, null );
1689
1741
  }
1690
- if ( method_exists( 'WC_Subscriptions_Switcher', 'cart_contains_switches' ) && \WC_Subscriptions_Switcher::cart_contains_switches( 'any' ) ) {
1691
- return true;
1742
+ // Decode entities so a literal code (e.g. one containing "&") still
1743
+ // matches. The strict empty-string check keeps a coupon code of "0" valid.
1744
+ $coupon_code = html_entity_decode( (string) $coupon_code, ENT_QUOTES );
1745
+ if ( '' === $coupon_code || ! function_exists( 'wc_coupons_enabled' ) || ! \wc_coupons_enabled() ) {
1746
+ return;
1692
1747
  }
1693
- if ( $cart->needs_shipping_address() ) {
1694
- $shipping = \WC()->shipping;
1695
- $packages = $shipping->get_packages();
1696
- $totals = $cart->get_totals();
1697
- $shipping_rates = [];
1698
-
1699
- // Find all the shipping rates that apply to the current transaction.
1700
- foreach ( $packages as $package ) {
1701
- if ( ! empty( $package['rates'] ) ) {
1702
- foreach ( $package['rates'] as $rate_key => $rate ) {
1703
- $shipping_rates[ $rate_key ] = $rate;
1704
- }
1705
- }
1748
+ $coupon = new \WC_Coupon( $coupon_code );
1749
+ $discounts = new \WC_Discounts( \WC()->cart );
1750
+ if ( true === $discounts->is_coupon_valid( $coupon ) && \WC()->cart->apply_coupon( $coupon_code ) ) {
1751
+ // apply_coupon() queues a "Coupon code applied successfully." success
1752
+ // notice; clear it so the auto-apply stays silent for the reader.
1753
+ if ( function_exists( 'wc_clear_notices' ) ) {
1754
+ \wc_clear_notices();
1706
1755
  }
1707
-
1708
- // Show details if shipping requires a fee or if there are multiple shipping rates to choose from.
1709
- if ( (float) $totals['total'] !== (float) $totals['subtotal'] || 1 < count( array_values( $shipping_rates ) ) ) {
1710
- return true;
1756
+ if ( \WC()->session ) {
1757
+ \WC()->session->set( self::AUTO_APPLIED_COUPON_SESSION_KEY, \wc_format_coupon_code( $coupon_code ) );
1711
1758
  }
1712
1759
  }
1760
+ }
1713
1761
 
1714
- if ( class_exists( 'WC_Subscriptions_Cart' ) && \WC_Subscriptions_Cart::cart_contains_subscription() ) {
1715
- return true;
1716
- }
1717
-
1718
- // Show details if the cart contains a subscription renewal.
1719
- if ( function_exists( 'wcs_cart_contains_renewal' ) && \wcs_cart_contains_renewal() ) {
1720
- return true;
1762
+ /**
1763
+ * Whether the in-modal coupon form should be hidden.
1764
+ *
1765
+ * True when a coupon was auto-applied from a Checkout Button block (tracked
1766
+ * in the session) and is still applied to the cart. The reader never entered
1767
+ * the code, so they should not be prompted to add or stack coupons. If they
1768
+ * remove the auto-applied coupon, the form is shown again.
1769
+ *
1770
+ * @return bool
1771
+ */
1772
+ public static function should_hide_coupon_form() {
1773
+ if ( ! function_exists( 'WC' ) || ! \WC()->session || ! \WC()->cart ) {
1774
+ return false;
1721
1775
  }
1776
+ $auto_applied = \WC()->session->get( self::AUTO_APPLIED_COUPON_SESSION_KEY );
1777
+ return ! empty( $auto_applied ) && in_array( $auto_applied, \WC()->cart->get_applied_coupons(), true );
1778
+ }
1722
1779
 
1723
- return false;
1780
+ /**
1781
+ * Whether to show order details table.
1782
+ *
1783
+ * @return bool
1784
+ */
1785
+ public static function should_show_order_details() {
1786
+ // The transaction details table is always shown for any non-empty cart. Simple
1787
+ // single-item carts previously hid it and relied on the static price-summary card
1788
+ // above the form; that card was removed, so the real, deal-accurate order details
1789
+ // must always be visible. Guard against a missing cart (early hooks / edge AJAX).
1790
+ $cart = \WC()->cart;
1791
+ return $cart && ! $cart->is_empty();
1724
1792
  }
1725
1793
 
1726
1794
  /**
@@ -1980,112 +2048,6 @@ final class Modal_Checkout {
1980
2048
  );
1981
2049
  }
1982
2050
 
1983
- /**
1984
- * Get validated cart item context for the modal checkout product summary.
1985
- *
1986
- * @param \WC_Cart $cart Cart object.
1987
- *
1988
- * @return array|null
1989
- */
1990
- private static function get_cart_product_summary_context( $cart ) {
1991
- if ( ! $cart || 1 !== $cart->get_cart_contents_count() ) {
1992
- return null;
1993
- }
1994
- $cart_item_key = array_key_first( $cart->get_cart() );
1995
- $cart_item = $cart->get_cart_item( $cart_item_key );
1996
-
1997
- // phpcs:disable WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- WooCommerce hooks.
1998
- $product = apply_filters( 'woocommerce_cart_item_product', $cart_item['data'], $cart_item, $cart_item_key );
1999
- $is_visible = $product && $product->exists() && $cart_item['quantity'] > 0 && apply_filters( 'woocommerce_checkout_cart_item_visible', true, $cart_item, $cart_item_key );
2000
- // phpcs:enable
2001
-
2002
- return [
2003
- 'cart_item_key' => $cart_item_key,
2004
- 'cart_item' => $cart_item,
2005
- 'product' => $product,
2006
- 'is_visible' => $is_visible,
2007
- ];
2008
- }
2009
-
2010
- /**
2011
- * Get the cart product summary shown at the top of modal checkout.
2012
- *
2013
- * @param \WC_Cart|null $cart Cart object.
2014
- * @param array|null $context Validated cart item context.
2015
- */
2016
- private static function get_cart_product_summary( $cart = null, $context = null ) {
2017
- if ( ! $cart ) {
2018
- if ( ! function_exists( 'WC' ) ) {
2019
- return '';
2020
- }
2021
- $cart = \WC()->cart;
2022
- }
2023
- $context = $context ? $context : self::get_cart_product_summary_context( $cart );
2024
- if ( ! $context || ! $context['is_visible'] ) {
2025
- return '';
2026
- }
2027
-
2028
- $cart_item_key = $context['cart_item_key'];
2029
- $cart_item = $context['cart_item'];
2030
- $product = $context['product'];
2031
- $allowed_html = self::get_cart_product_summary_allowed_html();
2032
- // phpcs:disable WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- WooCommerce hooks.
2033
- $product_name = apply_filters(
2034
- 'woocommerce_cart_item_name',
2035
- $product->get_name(),
2036
- $cart_item,
2037
- $cart_item_key
2038
- );
2039
- $product_subtotal = apply_filters(
2040
- 'woocommerce_cart_item_subtotal',
2041
- $cart->get_product_subtotal( $product, $cart_item['quantity'] ),
2042
- $cart_item,
2043
- $cart_item_key
2044
- );
2045
- // phpcs:enable
2046
-
2047
- $summary = $product_name . ': ';
2048
- if ( function_exists( 'wc_get_formatted_cart_item_data' ) ) {
2049
- $summary .= wc_get_formatted_cart_item_data( $cart_item );
2050
- }
2051
- $summary .= $product_subtotal;
2052
- return wp_kses( $summary, $allowed_html );
2053
- }
2054
-
2055
- /**
2056
- * Get allowed HTML for the modal checkout product summary.
2057
- *
2058
- * @return array Allowed HTML tags and attributes.
2059
- */
2060
- private static function get_cart_product_summary_allowed_html() {
2061
- $allowed_html = wp_kses_allowed_html( 'post' );
2062
-
2063
- $allowed_html['bdi'] = [
2064
- 'class' => true,
2065
- 'dir' => true,
2066
- ];
2067
-
2068
- if ( ! isset( $allowed_html['span'] ) ) {
2069
- $allowed_html['span'] = [];
2070
- }
2071
- $allowed_html['span']['class'] = true;
2072
-
2073
- if ( ! isset( $allowed_html['small'] ) ) {
2074
- $allowed_html['small'] = [];
2075
- }
2076
- $allowed_html['small']['class'] = true;
2077
-
2078
- return $allowed_html;
2079
- }
2080
-
2081
- /**
2082
- * Get the updated cart product summary for JavaScript.
2083
- */
2084
- public static function get_cart_product_summary_js() {
2085
- echo self::get_cart_product_summary(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
2086
- wp_die();
2087
- }
2088
-
2089
2051
  /**
2090
2052
  * Get the updated price for updating the "Place order" button.
2091
2053
  */
@@ -2112,22 +2074,23 @@ final class Modal_Checkout {
2112
2074
  if ( 1 !== $cart->get_cart_contents_count() ) {
2113
2075
  return;
2114
2076
  }
2115
- $summary_context = self::get_cart_product_summary_context( $cart );
2116
- $class_prefix = self::get_class_prefix();
2077
+ $cart_item_key = array_key_first( $cart->get_cart() );
2078
+ $cart_item = $cart->get_cart_item( $cart_item_key );
2117
2079
  ?>
2118
- <div class="<?php echo esc_attr( "order-details-summary {$class_prefix}__box {$class_prefix}__box--text-center" ); ?>">
2119
2080
  <?php
2120
- if ( $summary_context && $summary_context['is_visible'] ) :
2081
+ // phpcs:disable WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- WooCommerce hooks.
2082
+ $_product = apply_filters( 'woocommerce_cart_item_product', $cart_item['data'], $cart_item, $cart_item_key );
2083
+ // Hidden data carrier only — the visible price-summary card was removed. The modal
2084
+ // dialog (modal.js) still reads this element's data-checkout for the RAS
2085
+ // checkout_completed event and post-auth checkout routing. It is intentionally not
2086
+ // gated on woocommerce_checkout_cart_item_visible (it carries data, not a visible
2087
+ // row), so the JS anchor and analytics survive a plugin hiding the cart item.
2088
+ if ( $_product && $_product->exists() && $cart_item['quantity'] > 0 ) :
2121
2089
  ?>
2122
- <p id="modal-checkout-product-details" data-checkout='<?php echo wp_json_encode( Checkout_Data::get_checkout_data( $cart ) ); ?>'>
2123
- <strong>
2124
- <?php echo self::get_cart_product_summary( $cart, $summary_context ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
2125
- </strong>
2126
- </p>
2090
+ <p id="modal-checkout-product-details" <?php Checkout_Data::print_data_checkout_attr( Checkout_Data::get_checkout_data( $cart ) ); ?> hidden></p>
2127
2091
  <?php
2128
2092
  endif;
2129
2093
  ?>
2130
- </div>
2131
2094
  <?php
2132
2095
  }
2133
2096
 
@@ -240,6 +240,7 @@ class Newspack_Blocks {
240
240
  'recaptcha_url' => admin_url( 'admin.php?page=newspack-settings' ),
241
241
  'custom_taxonomies' => self::get_custom_taxonomies(),
242
242
  'can_use_name_your_price' => self::can_use_name_your_price(),
243
+ 'coupons_enabled' => function_exists( 'wc_coupons_enabled' ) && \wc_coupons_enabled(),
243
244
  'tier_amounts_template' => self::get_formatted_amount(),
244
245
  'currency' => function_exists( 'get_woocommerce_currency' ) ? \get_woocommerce_currency() : 'USD',
245
246
  ];
@@ -231,6 +231,29 @@ final class Checkout_Data {
231
231
  return $children;
232
232
  }
233
233
 
234
+ /**
235
+ * Build a `data-checkout` HTML attribute for a checkout-data payload.
236
+ *
237
+ * Centralizes wp_json_encode + esc_attr so the encoding and escaping live in a
238
+ * single place and can't be omitted by callers. Mirrors the JS getCheckoutData()
239
+ * reader in src/modal-checkout/utils.js.
240
+ *
241
+ * @param array $data Checkout-data payload (e.g. from get_checkout_data()).
242
+ * @return string Escaped attribute, e.g. data-checkout='{...}'.
243
+ */
244
+ public static function data_checkout_attr( array $data ) {
245
+ return sprintf( "data-checkout='%s'", esc_attr( wp_json_encode( $data ) ) );
246
+ }
247
+
248
+ /**
249
+ * Echo a `data-checkout` HTML attribute for a checkout-data payload.
250
+ *
251
+ * @param array $data Checkout-data payload.
252
+ */
253
+ public static function print_data_checkout_attr( array $data ) {
254
+ echo self::data_checkout_attr( $data ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- data_checkout_attr() escapes via esc_attr().
255
+ }
256
+
234
257
  /**
235
258
  * Returns checkout data given a product, product variation, cart or order object.
236
259
  *
@@ -7,7 +7,7 @@
7
7
  * Author URI: https://newspack.com/
8
8
  * Text Domain: newspack-blocks
9
9
  * Domain Path: /languages
10
- * Version: 4.28.0
10
+ * Version: 4.29.0-alpha.2
11
11
  *
12
12
  * @package Newspack_Blocks
13
13
  */
@@ -15,7 +15,7 @@
15
15
  define( 'NEWSPACK_BLOCKS__PLUGIN_FILE', __FILE__ );
16
16
  define( 'NEWSPACK_BLOCKS__BLOCKS_DIRECTORY', 'dist/' );
17
17
  define( 'NEWSPACK_BLOCKS__PLUGIN_DIR', plugin_dir_path( NEWSPACK_BLOCKS__PLUGIN_FILE ) );
18
- define( 'NEWSPACK_BLOCKS__VERSION', '4.28.0' );
18
+ define( 'NEWSPACK_BLOCKS__VERSION', '4.29.0-alpha.2' );
19
19
 
20
20
  require_once NEWSPACK_BLOCKS__PLUGIN_DIR . 'includes/class-newspack-blocks.php';
21
21
  require_once NEWSPACK_BLOCKS__PLUGIN_DIR . 'includes/class-newspack-blocks-api.php';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@automattic/newspack-blocks",
3
- "version": "4.28.0",
3
+ "version": "4.29.0-alpha.2",
4
4
  "author": "Automattic",
5
5
  "description": "=== Newspack Blocks === Contributors: (this should be a list of wordpress.org userid's) Donate link: https://example.com/ Tags: comments, spam Requires at least: 4.5 Tested up to: 5.1.1 Stable tag: 0.1.0 License: GPLv2 or later License URI: https://www.gnu.org/licenses/gpl-2.0.html",
6
6
  "repository": {
@@ -46,8 +46,8 @@
46
46
  "dependencies": {
47
47
  "classnames": "^2.5.1",
48
48
  "lodash": "^4.17.21",
49
- "newspack-colors": "1.1.0",
50
- "newspack-icons": "1.0.8",
49
+ "newspack-colors": "1.1.1-alpha.1",
50
+ "newspack-icons": "1.1.0-alpha.1",
51
51
  "redux": "^5.0.0",
52
52
  "redux-saga": "^1.5.0",
53
53
  "regenerator-runtime": "^0.14.1",
@@ -55,8 +55,8 @@
55
55
  },
56
56
  "devDependencies": {
57
57
  "@types/lodash": "^4.17.23",
58
- "newspack-components": "4.5.0",
59
- "newspack-scripts": "5.9.8"
58
+ "newspack-components": "4.6.0-alpha.1",
59
+ "newspack-scripts": "5.10.0-alpha.1"
60
60
  },
61
61
  "overrides": {
62
62
  "path-to-regexp": "1.7.0"
@@ -53,6 +53,9 @@
53
53
  },
54
54
  "width": {
55
55
  "type": "number"
56
+ },
57
+ "coupon": {
58
+ "type": "string"
56
59
  }
57
60
  },
58
61
  "supports": {