summaryrefslogtreecommitdiffstats
path: root/assets/js/cepis-woocommerce-blocks.js
diff options
context:
space:
mode:
authorMurilo Ijanc <[email protected]>2026-07-26 16:41:48 -0300
committerMurilo Ijanc <[email protected]>2026-07-26 16:47:14 -0300
commit535dbda6b48171f8115b455e54790de94d97d42a (patch)
tree5c0410011e61a883bdf063ee612875a637fb1d22 /assets/js/cepis-woocommerce-blocks.js
downloadwoocommerce-535dbda6b48171f8115b455e54790de94d97d42a.tar.gz
Initial importv0.1.0
Diffstat (limited to 'assets/js/cepis-woocommerce-blocks.js')
-rw-r--r--assets/js/cepis-woocommerce-blocks.js243
1 files changed, 243 insertions, 0 deletions
diff --git a/assets/js/cepis-woocommerce-blocks.js b/assets/js/cepis-woocommerce-blocks.js
new file mode 100644
index 0000000..bcbab93
--- /dev/null
+++ b/assets/js/cepis-woocommerce-blocks.js
@@ -0,0 +1,243 @@
+/**
+ * WooCommerce Cart/Checkout Blocks.
+ *
+ * The block checkout is React and its address inputs are controlled: a value
+ * written into the DOM from outside is discarded on the next render, and the
+ * `wc/store/cart` data store — which is what the order is actually built from —
+ * never hears about it. So nothing here touches a field. The postcode is read
+ * from the data store, and the resolved address is written back through the
+ * store's own `setShippingAddress` / `setBillingAddress` actions, which is the
+ * documented extension point. React then re-renders the inputs itself.
+ *
+ * Only the request layer is shared with the classic integration:
+ * `CepisAutofill.lookup` is the widget's HTTP client without its DOM half —
+ * same cache, same error codes, same handling of 400, 404, 429 and 5xx.
+ *
+ * @module
+ */
+
+(function () {
+ var config = window.cepisWooCommerce;
+ var data = window.wp && window.wp.data;
+
+ if (!config || !config.fields || !window.CepisAutofill || !data || !data.select) {
+ return;
+ }
+
+ var CART = "wc/store/cart";
+ var NOTICES = "core/notices";
+
+ /**
+ * Where a notice has to be rendered for the Checkout block to show it.
+ */
+ var NOTICE_CONTEXT = "wc/checkout";
+
+ /**
+ * One id, so a new failure replaces the previous message instead of stacking.
+ */
+ var NOTICE_ID = "cepis-woocommerce-lookup";
+
+ var SECTIONS = {
+ shipping: { address: "shippingAddress", write: "setShippingAddress" },
+ billing: { address: "billingAddress", write: "setBillingAddress" },
+ };
+
+ var progress = {
+ shipping: { cep: "", timer: 0, written: {} },
+ billing: { cep: "", timer: 0, written: {} },
+ };
+
+ /**
+ * The one field the shopper is never asked about.
+ *
+ * A street name is worth protecting — the shopper may know better than the
+ * database. A state is not: the CEP decides it. WooCommerce also starts the
+ * checkout with the store's own state already selected, which the `"auto"`
+ * rule would read as the shopper's choice and never touch, quietly filing a
+ * Rio address under São Paulo.
+ */
+ var ALWAYS = "uf";
+
+ /**
+ * Whether a field may be written.
+ *
+ * Mirrors the widget's `overwrite` option: `"auto"` protects anything the
+ * shopper typed but replaces what a previous lookup wrote, so correcting the
+ * street by hand survives the next CEP.
+ *
+ * @param {string} field The API's name for the field.
+ * @param {string} current Value the store holds now.
+ * @param {string|undefined} written Value this plugin last wrote there.
+ * @returns {boolean}
+ */
+ function writable(field, current, written) {
+ if (field === ALWAYS || config.overwrite === true) {
+ return true;
+ }
+ if (current.trim() === "") {
+ return true;
+ }
+ if (config.overwrite === false) {
+ return false;
+ }
+ return written === current;
+ }
+
+ var pending = {};
+
+ /**
+ * Look a CEP up once, however many sections want it.
+ *
+ * With "use the same address for billing" on — the default — the shipping
+ * and the billing observer see the same postcode land in the same tick. The
+ * widget's own cache is only filled once a response is back, so without this
+ * the store would send two identical requests for every CEP a shopper types.
+ *
+ * @param {string} digits The eight digits.
+ * @returns {Promise<Record<string, any>>}
+ */
+ function resolve(digits) {
+ if (!pending[digits]) {
+ var forget = function () {
+ delete pending[digits];
+ };
+
+ pending[digits] = window.CepisAutofill.lookup(digits, {
+ baseUrl: config.baseUrl,
+ timeoutMs: config.timeoutMs,
+ });
+ pending[digits].then(forget, forget);
+ }
+
+ return pending[digits];
+ }
+
+ /**
+ * Show one message in the Checkout block's own notice area.
+ *
+ * @param {string} text Message, already translated.
+ */
+ function announce(text) {
+ data.dispatch(NOTICES).createNotice("error", text, {
+ id: NOTICE_ID,
+ context: NOTICE_CONTEXT,
+ isDismissible: true,
+ });
+ }
+
+ /** Take the message away. */
+ function silence() {
+ data.dispatch(NOTICES).removeNotice(NOTICE_ID, NOTICE_CONTEXT);
+ }
+
+ /**
+ * Write a resolved address into the store.
+ *
+ * @param {string} name Section name, `shipping` or `billing`.
+ * @param {Record<string, any>} address The API's answer.
+ */
+ function apply(name, address) {
+ var customer = data.select(CART).getCustomerData();
+ var current = (customer && customer[SECTIONS[name].address]) || {};
+ var written = progress[name].written;
+ var patch = {};
+ var changed = false;
+
+ Object.keys(config.fields).forEach(function (field) {
+ var key = config.fields[field];
+ var value = address[field];
+
+ if (!key || typeof value !== "string" || value === "") {
+ return;
+ }
+
+ var held = current[key] == null ? "" : String(current[key]);
+ if (!writable(field, held, written[key])) {
+ return;
+ }
+
+ patch[key] = value;
+ written[key] = value;
+ changed = true;
+ });
+
+ if (changed) {
+ data.dispatch(CART)[SECTIONS[name].write](patch);
+ }
+ }
+
+ /**
+ * Resolve one CEP and write what comes back.
+ *
+ * @param {string} name Section name, `shipping` or `billing`.
+ * @param {string} digits The eight digits.
+ */
+ function run(name, digits) {
+ silence();
+
+ resolve(digits).then(
+ function (address) {
+ if (progress[name].cep === digits) {
+ apply(name, address);
+ }
+ },
+ function (error) {
+ if (progress[name].cep !== digits || error.code === "aborted") {
+ return;
+ }
+ announce(config.messages[error.code] || error.message);
+ },
+ );
+ }
+
+ /**
+ * React to whatever the store now holds for one address.
+ *
+ * @param {string} name Section name, `shipping` or `billing`.
+ * @param {Record<string, any>|undefined} address Address from the store.
+ */
+ function observe(name, address) {
+ var seen = progress[name];
+
+ if (!address || String(address.country || "") !== "BR") {
+ return;
+ }
+
+ var digits = String(address.postcode || "").replace(/\D+/g, "");
+
+ if (digits.length !== 8) {
+ if (seen.cep !== "") {
+ seen.cep = "";
+ window.clearTimeout(seen.timer);
+ silence();
+ }
+ return;
+ }
+
+ if (digits === seen.cep) {
+ return;
+ }
+
+ seen.cep = digits;
+ window.clearTimeout(seen.timer);
+ seen.timer = window.setTimeout(function () {
+ run(name, digits);
+ }, config.debounceMs);
+ }
+
+ data.subscribe(function () {
+ var store = data.select(CART);
+ if (!store || typeof store.getCustomerData !== "function") {
+ return;
+ }
+
+ var customer = store.getCustomerData();
+ if (!customer) {
+ return;
+ }
+
+ Object.keys(SECTIONS).forEach(function (name) {
+ observe(name, customer[SECTIONS[name].address]);
+ });
+ }, CART);
+})();