Customizer – komunikacja przez postMessage

Wprowadzenie

Customizer komunikuje się ze stroną hostującą przez postMessage. Używa wspólnego handshake z nonce, a następnie customizerowego żądania sesji, które zwraca token i klucz koszyka.

Konfiguracja:

  1. Otwórz zakładkę Ustawienia e-commerce w aplikacji Alter Product.
  2. W sekcji Integracja ustaw domenę, na której zostanie osadzony iframe (np. https://twojastrona.pl lub http://localhost:3000).

Przepływ:

  1. Twoja strona ładuje iframe Customizera.
  2. Iframe wysyła ALTER_CHILD_HELLO, a strona odpowiada ALTER_PARENT_ACK z tym samym nonce.
  3. Iframe prosi o sesję przez ALTER_CUSTOMIZER_INIT_SESSION z payloadem zawierającym alterProductId, uiDesignId oraz mode.
  4. Backend sklepu tworzy sesję przez Alter Product Public API, a strona odpowiada ALTER_CUSTOMIZER_SESSION_READY z token, cartKey i mode.
  5. Po zapisie Customizer wysyła ALTER_CUSTOMIZER_ADD_TO_CART dla nowej pozycji koszyka albo ALTER_CUSTOMIZER_UPDATE_DESIGN dla istniejącej pozycji.

Metadane wariantów skonfigurowane w Product Configuration są dołączane do payloadu.

Wiadomości wysyłane przez Customizer

TypOpis
ALTER_CHILD_HELLOStart handshake; wiadomość zawiera nonce
ALTER_CUSTOMIZER_INIT_SESSIONŻąda tokenu sesji Customizera i klucza koszyka od strony hostującej
ALTER_CUSTOMIZER_ADD_TO_CARTKliknięcie przycisku „Dodaj do koszyka” wysyłane po pomyślnym zapisaniu projektu — zintegrowane z Twoim koszykiem/sklepem
ALTER_CUSTOMIZER_UPDATE_DESIGNZapis zmian dla istniejącej pozycji koszyka lub zamówienia
ALTER_WP_CUSTOMIZER_ORDER_REQUESTŻądanie WordPress/WooCommerce pobrania istniejącego zamówienia klienta
ALTER_WP_CUSTOMIZER_ORDER_SAVE_REQUESTŻądanie WordPress/WooCommerce zapisu danych zamówienia i uploadów
ALTER_WP_RUNTIME_CONTEXT_REQUESTŻądanie kontekstu runtime WordPress/WooCommerce
ALTER_WP_LOCAL_DESIGN_REQUESTŻądanie bootstrapu local design w WordPress przy wp_local_design=1

Gdy klient kliknie „Dodaj do koszyka” w Customizerze, tworzony jest nowy rekord zamówienia ze statusem domyślnym: pending, co pozwala klientowi kontynuować edycję projektu (np. poprzez link: https://alterproduct.com/app/customizer/{customizerId}/{orderId}). Gdy status zmieni się na inny niż „pending”, edycja zostaje zablokowana — klient widzi zamrożoną wersję projektu.

Wiadomości odbierane przez Customizer

TypOpis
ALTER_PARENT_ACKPotwierdzenie handshake, które musi zawierać nonce
ALTER_CUSTOMIZER_SESSION_READYPrzekazuje token, cartKey i mode do autoryzacji Customizera
ALTER_CUSTOMIZER_SESSION_ERRORUtworzenie sesji Customizera nie powiodło się
ALTER_WP_CUSTOMIZER_ORDER_RESPONSEOdpowiedź WordPress/WooCommerce z istniejącym zamówieniem klienta
ALTER_WP_CUSTOMIZER_ORDER_SAVE_RESPONSEOdpowiedź WordPress/WooCommerce po zapisie zamówienia klienta
ALTER_WP_RUNTIME_CONTEXT_RESPONSEOdpowiedź z kontekstem runtime WordPress/WooCommerce
ALTER_WP_LOCAL_DESIGN_RESPONSEOdpowiedź bootstrapu local design w WordPress

Pełny przykład (HTML + JS)

1<!DOCTYPE html>
2<html lang="en">
3<head>
4  <meta charset="UTF-8" />
5  <meta name="viewport" content="width=device-width, initial-scale=1" />
6  <title>Alter Product Customizer — postMessage (Handshake + Token)</title>
7  <style>
8    body { font-family: system-ui, -apple-system, Segoe UI, Roboto, sans-serif; margin: 24px; }
9    iframe { width: 100%; height: 720px; border: 0; display: block; border-radius: 12px; }
10    pre { background: #0b1020; color: #d7e1ff; padding: 12px; border-radius: 12px; overflow:auto; }
11  </style>
12</head>
13<body>
14  <h1>My E-commerce</h1>
15
16  <iframe
17    id="customizerWidget"
18    src="https://alterproduct.com/app/customizer/1"
19    title="Alter Product Customizer"
20    allowfullscreen>
21  </iframe>
22
23  <h3>Logs</h3>
24  <pre id="log"></pre>
25
26  <script>
27    const IFRAME_ORIGIN = 'https://alterproduct.com';
28    const iframe = document.getElementById('customizerWidget');
29    const logEl = document.getElementById('log');
30
31    let handshakeOk = false;
32
33    function log(...args) {
34      const line = args.map(a => (typeof a === 'string' ? a : JSON.stringify(a, null, 2))).join(' ');
35      logEl.textContent += line + '\n';
36    }
37
38    function postToIframe(payload) {
39      iframe.contentWindow.postMessage(payload, IFRAME_ORIGIN);
40    }
41
42    /**
43     * Your backend owns this endpoint and should create a Customizer embed session
44     * using server-side credentials. Expected response:
45     * { sessionToken: "...", cartKey: "...", mode: "view" | "edit" }
46     */
47    async function getCustomizerSessionFromYourBackend(payload) {
48      const res = await fetch('/api/alter/customizer-session', {
49        method: 'POST',
50        headers: { 'Content-Type': 'application/json' },
51        body: JSON.stringify(payload || {}),
52      });
53
54      if (!res.ok) throw new Error('Failed to create customizer session');
55
56      const data = await res.json();
57      if (!data || !data.sessionToken || !data.cartKey) {
58        throw new Error('Missing sessionToken or cartKey');
59      }
60
61      return data;
62    }
63
64    window.addEventListener('message', async (event) => {
65      // ✅ 1) Validate origin
66      if (event.origin !== IFRAME_ORIGIN) return;
67
68      // ✅ 2) Validate source
69      if (event.source !== iframe.contentWindow) return;
70
71      const msg = event.data || {};
72      if (!msg.type || typeof msg.type !== 'string') return;
73
74      // -----------------------
75      // A) HANDSHAKE
76      // Customizer -> Host: ALTER_CHILD_HELLO { nonce }
77      // Host       -> Customizer: ALTER_PARENT_ACK { nonce }
78      // -----------------------
79      if (msg.type === 'ALTER_CHILD_HELLO') {
80        const nonce = msg.nonce;
81        if (!nonce || typeof nonce !== 'string') return;
82
83        handshakeOk = true;
84        log('[Customizer] -> Host: ALTER_CHILD_HELLO', { nonce });
85
86        log('[Host] -> Customizer: ALTER_PARENT_ACK');
87        postToIframe({ type: 'ALTER_PARENT_ACK', nonce });
88        return;
89      }
90
91      // Ignore everything until handshake is done
92      if (!handshakeOk) return;
93
94      // -----------------------
95      // B) CUSTOMIZER SESSION INIT
96      // Customizer -> Host: ALTER_CUSTOMIZER_INIT_SESSION { payload: { alterProductId, uiDesignId, mode } }
97      // Host       -> Customizer: ALTER_CUSTOMIZER_SESSION_READY { token, cartKey, mode }
98      // -----------------------
99      if (msg.type === 'ALTER_CUSTOMIZER_INIT_SESSION') {
100        try {
101          const payload = msg.payload || {};
102          const alterProductId = Number(payload.alterProductId || 0);
103          const uiDesignId = Number(payload.uiDesignId || 0);
104          const mode = payload.mode === 'edit' ? 'edit' : 'view';
105
106          log('[Customizer] -> Host: ALTER_CUSTOMIZER_INIT_SESSION', {
107            alterProductId,
108            uiDesignId,
109            mode,
110          });
111
112          const session = await getCustomizerSessionFromYourBackend({
113            alterProductId,
114            uiDesignId,
115            mode,
116          });
117
118          log('[Host] -> Customizer: ALTER_CUSTOMIZER_SESSION_READY');
119          postToIframe({
120            type: 'ALTER_CUSTOMIZER_SESSION_READY',
121            token: session.sessionToken,
122            cartKey: session.cartKey,
123            mode: session.mode || mode,
124          });
125        } catch (e) {
126          log('[Host] -> Customizer: ALTER_CUSTOMIZER_SESSION_ERROR', String(e && e.message ? e.message : e));
127          postToIframe({
128            type: 'ALTER_CUSTOMIZER_SESSION_ERROR',
129            message: String(e && e.message ? e.message : e),
130          });
131        }
132        return;
133      }
134
135      // -----------------------
136      // C) Customizer events
137      // -----------------------
138      if (msg.type === 'ALTER_CUSTOMIZER_ADD_TO_CART') {
139        log('[Customizer] -> Host: ALTER_CUSTOMIZER_ADD_TO_CART', msg.payload);
140        // Send payload to your backend and add item to cart in your e-commerce system
141        return;
142      }
143
144      if (msg.type === 'ALTER_CUSTOMIZER_UPDATE_DESIGN') {
145        log('[Customizer] -> Host: ALTER_CUSTOMIZER_UPDATE_DESIGN', msg.payload);
146        // Update an existing cart line/order in your e-commerce system
147        return;
148      }
149    });
150  </script>
151</body>
152</html>

Przykładowy payload

{
  "id": 481,
  "customizerId": 10,
  "userDesignId": 10,
  "isSeenByOwner": false,
  "orderStatus": "shopping_cart",
  "createdAt": "2026-05-28T10:15:00.000Z",
  "customizerOrderURL": "https://alterproduct.com/app/customizer/10/481",
  "customizerURL": "https://alterproduct.com/app/customizer/10",
  "customizerName": "Mug 450ml (15oz)",
  "totalPrice": {
    "value": 24.99,
    "currency": "EUR"
  },
  "productGroup": {
    "id": 1,
    "name": {
      "pl": "Kubek 450ml (15oz)",
      "en": "Mug 450ml (15oz)"
    }
  },
  "productItems": [
    {
      "id": 901,
      "model3d": {
        "id": 3
      },
      "size": {
        "id": 9,
        "name": {
          "pl": "450ml (15oz)",
          "en": "450ml (15oz)"
        },
        "measureSize": {
          "D": 8.65,
          "H": 11.95
        },
        "externalMapping": {
          "attribute": {
            "internalId": 123,
            "slug": "pa_size",
            "label": "Size"
          },
          "term": {
            "internalId": 456,
            "slug": "450ml-15oz",
            "label": "450ml (15oz)"
          }
        }
      },
      "material": {
        "id": 3,
        "name": {
          "pl": "Ceramika",
          "en": "Ceramic"
        },
        "externalMapping": {
          "attribute": {
            "internalId": 124,
            "slug": "pa_material",
            "label": "Material"
          },
          "term": {
            "internalId": 457,
            "slug": "ceramic",
            "label": "Ceramic"
          }
        }
      },
      "printType": {
        "id": 5,
        "name": {
          "pl": "Sublimacja",
          "en": "Sublimation"
        },
        "externalMapping": {
          "attribute": {
            "internalId": 125,
            "slug": "pa_printing-method",
            "label": "Printing method"
          },
          "term": {
            "internalId": 458,
            "slug": "sublimation",
            "label": "Sublimation"
          }
        }
      },
      "printingMethod": {
        "id": 5,
        "name": {
          "pl": "Sublimacja",
          "en": "Sublimation"
        },
        "externalMapping": {
          "attribute": {
            "internalId": 125,
            "slug": "pa_printing-method",
            "label": "Printing method"
          },
          "term": {
            "internalId": 458,
            "slug": "sublimation",
            "label": "Sublimation"
          }
        }
      },
      "color": {
        "id": 11,
        "name": {
          "pl": "Domyślny",
          "en": "Default"
        },
        "hex": "#FFFFFF",
        "customColor": false,
        "pickedColors": {},
        "patternId": null,
        "externalMapping": {
          "attribute": {
            "internalId": 126,
            "slug": "pa_color",
            "label": "Color"
          },
          "term": {
            "internalId": 459,
            "slug": "default",
            "label": "Default"
          }
        }
      },
      "variant": {
        "id": 11,
        "productGroupId": 1,
        "productModel3dId": 3,
        "sizeId": 9,
        "materialId": 3,
        "printingMethodId": 5,
        "colorId": 11,
        "metadata": {
          "sku": "MUG-450-WHITE"
        },
        "minOrderQuantity": 1,
        "processingTime": null,
        "stockQuantity": null,
        "volume": null,
        "weight": null
      },
      "unitPrice": {
        "value": 24.99,
        "currency": "EUR"
      },
      "totalPrice": {
        "value": 24.99,
        "currency": "EUR"
      },
      "quantity": 1,
      "mockups": [
        {
          "productModel3dId": 3,
          "img": {
            "thumb": "https://api.example.com/file/protected/user_1/10/e-commerce/orders/481/mockup-thumb.webp",
            "large": "https://api.example.com/file/protected/user_1/10/e-commerce/orders/481/mockup-large.webp"
          }
        }
      ],
      "updatedAt": "2026-05-28T10:15:10.000Z"
    }
  ],
  "cartKey": "wc_cart_item_key"
}