// 1.初期処理 // フォーム・画面要素の取得 const form = document.getElementById('merchantForm'); const formPanel = document.getElementById('formPanel'); const confirmPanel = document.getElementById('confirmPanel'); const completePanel = document.getElementById('completePanel'); const confirmList = document.getElementById('confirmList'); const errorBox = document.getElementById('errorBox'); const receiptDateEl = document.getElementById('receiptDate'); const sendBtn = document.getElementById('sendBtn'); const formStartedAt = document.getElementById('formStartedAt'); const storeContainer = document.getElementById('storeContainer'); const addStoreBtn = document.getElementById('addStoreBtn'); const removeStoreBtn = document.getElementById('removeStoreBtn'); //URL const API_ENDPOINT = 'https://defaultee0f0c109f864a4d8a2605c1ff2ff7.20.environment.api.powerplatform.com:443/powerautomate/automations/direct/cu/11/workflows/0265df7836f949599b061ad50c5f62d5/triggers/manual/paths/invoke?api-version=1&sp=%2Ftriggers%2Fmanual%2Frun&sv=1.0&sig=b8zG0I4S36eoxtO17jdt1eU_udf0ADWhesrZbPNY4mg'; const ADDRESS_API_ENDPOINT = 'https://defaultee0f0c109f864a4d8a2605c1ff2ff7.20.environment.api.powerplatform.com:443/powerautomate/automations/direct/cu/01/workflows/46a1aef2a42844b992b22605f21c3ef4/triggers/manual/paths/invoke?api-version=1&sp=%2Ftriggers%2Fmanual%2Frun&sv=1.0&sig=PRvO8BGjnu4KRT7WxxMIrSLm-IY8kwNXuvSr3eZ5enU' initFormStartedAt(); //フォーム入力開始日時を記録 function initFormStartedAt() { formStartedAt.value = new Date().toISOString(); } // 処理処理end // 全角数字を半角数字へ変換 function normalizeDigits(value) { return value.trim().replace(/[0-9]/g, s => String.fromCharCode(s.charCodeAt(0) - 0xFEE0)); } //2.店舗追加・削除処理 // 店舗追加 addStoreBtn.addEventListener('click', () => { const storeBlocks = storeContainer.querySelectorAll('.store-block'); const newStoreBlock = storeBlocks[storeBlocks.length - 1].cloneNode(true); const storeNumber = storeBlocks.length + 1; newStoreBlock.querySelector('.storeName').value = ''; newStoreBlock.querySelector('.storeCategory').value = ''; newStoreBlock.querySelector('.storePhone').value = ''; newStoreBlock.querySelector('.storePostalCode').value = ''; newStoreBlock.querySelector('.storePrefecture').value = ''; newStoreBlock.querySelector('.storeCity').value = ''; newStoreBlock.querySelector('.storeAddress').value = ''; //住所候補をクリア const candidatesContainer = newStoreBlock.querySelector('.address-candidates'); candidatesContainer.replaceChildren(); candidatesContainer.hidden = true; //住所候補をクリアend newStoreBlock.querySelector('.storeNameLabel').textContent = `店舗名${storeNumber}`; newStoreBlock.querySelector('.storeCategoryLabel').textContent = `店舗業態${storeNumber}`; newStoreBlock.querySelector('.storePhoneLabel').textContent = `店舗電話番号${storeNumber}`; newStoreBlock.querySelector('.storePostalCodeLabel').textContent = `郵便番号${storeNumber}`; newStoreBlock.querySelector('.storePrefectureLabel').textContent = `都道府県${storeNumber}`; newStoreBlock.querySelector('.storeCityLabel').textContent = `市町村${storeNumber}`; newStoreBlock.querySelector('.storeAddressLabel').textContent = `番地${storeNumber}`; storeContainer.appendChild(newStoreBlock); }); // 店舗削除 removeStoreBtn.addEventListener('click', () => { const storeBlocks = storeContainer.querySelectorAll('.store-block'); if (storeBlocks.length > 1) { storeBlocks[storeBlocks.length - 1].remove(); } }); //店舗追加・削除処理end // 3.住所処理 // 「住所を取得」ボタン押下時の処理 storeContainer.addEventListener('click', event => { const button = event.target.closest('.get-address-btn'); if (!button) return; //店舗ブロックを取得 const storeBlock = button.closest('.store-block'); if (!storeBlock) return; //押されたボタンと同じ場所の郵便番号を取得 const postalCodeInput = storeBlock.querySelector('.storePostalCode'); if (!postalCodeInput) return; //住所取得処理呼び出し getAddressByPostalCode(postalCodeInput.value, button, storeBlock); }); // 住所取得処理 async function getAddressByPostalCode(postalCode, button, storeBlock) { if (!storeBlock) { alert('住所入力欄を特定できませんでした。'); return; } //郵便番号を全角→半角 const normalizedPostalCode = normalizeDigits(postalCode); // 郵便番号の入力チェック if (!/^\d{7}$/.test(normalizedPostalCode)) { alert('ハイフンなしで数字7桁で入力してください。'); return; } if (!ADDRESS_API_ENDPOINT || ADDRESS_API_ENDPOINT.includes('ここに住所取得用Power AutomateのURLを設定')) { alert('住所取得用の接続先が設定されていません。'); return; } // 二重押下防止 const originalText = button.textContent; button.disabled = true; button.textContent = '取得中...'; try { //住所取得API実行→結果をresponseに格納 const response = await fetch(ADDRESS_API_ENDPOINT, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ postalCode: normalizedPostalCode }), credentials: 'omit', cache: 'no-store' }); //住所取得API実行→結果をresponseに格納end if (!response.ok) { throw new Error('住所を取得できませんでした。'); } //jsが扱える形式に変換 const data = await response.json(); // 住所候補表示エリアを取得 const candidatesContainer = storeBlock.querySelector('.address-candidates'); if (!candidatesContainer) { alert('住所候補の表示領域を取得できませんでした。'); return; } // 前回の候補をクリア candidatesContainer.replaceChildren(); candidatesContainer.hidden = true; // 住所が存在しない場合 if (!Array.isArray(data.addresses) || data.addresses.length === 0) { alert('該当する住所が見つかりませんでした。'); return; } // 複数候補がある場合一件ずつ処理 if (data.addresses.length > 1) { //住所候補ボタン一件ずつ作成 data.addresses.forEach(address => { const candidateButton = document.createElement('button'); candidateButton.type = 'button'; candidateButton.className = 'address-candidate-btn'; candidateButton.textContent = `${address.pref_name || ''}${address.city_name || ''}${address.town_name || ''}`; //候補ボタンクリック時の処理 candidateButton.addEventListener('click', () => { applyAddressToForm(address, storeBlock); candidatesContainer.hidden = true; candidatesContainer.replaceChildren(); }); //住所候補表示エリアにボタンを追加 candidatesContainer.appendChild(candidateButton); }); //住所候補ボタン一件ずつ作成end candidatesContainer.hidden = false; return; } // 1件だけの場合は、そのままフォームへ反映 applyAddressToForm(data.addresses[0], storeBlock); } catch (error) { console.error('住所取得エラー:', error); alert( error.message || '住所を取得できませんでした。時間をおいて再度お試しください。' ); } finally { button.disabled = false; button.textContent = originalText; } } // APIから取得した住所をフォームへ反映 function applyAddressToForm(address, storeBlock) { storeBlock.querySelector('.storePrefecture').value = address.pref_name || ''; storeBlock.querySelector('.storeCity').value = `${address.city_name || ''}${address.town_name || ''}`; storeBlock.querySelector('.storeAddress').value = address.block_name || ''; } // 住所処理end // 4.確認ボタン処理 // 確認画面から送信するまで申込内容を保持 let currentPayload = null; // 確認画面に表示する項目名(共通部分) const labels = { businessName: '法人名(個人事業主名)', branchCode: '口座店番', accountType: '口座科目', accountNumber: '口座番号', contactEmail: '連絡先メールアドレス' }; // 確認ボタン押下時 form.addEventListener('submit', event => { event.preventDefault(); const data = getFormData(); const errors = validate(data); showErrors(errors); if (errors.length) return; currentPayload = data; renderConfirm(data); formPanel.hidden = true; confirmPanel.hidden = false; confirmPanel.scrollIntoView({ behavior: 'smooth' }); }); // フォームの入力値を取得し、送信用に整形 function getFormData() { const data = Object.fromEntries(new FormData(form).entries()); data.businessName = (data.businessName || '').trim(); data.contactEmail = (data.contactEmail || '').trim(); data.branchCode = normalizeDigits(data.branchCode || ''); data.accountType = (data.accountType || '').trim(); data.accountNumber = normalizeDigits(data.accountNumber || ''); data.website = (data.website || '').trim(); if (data.branchCode) { data.branchCode = data.branchCode.padStart(3, '0'); } if (data.accountNumber) { data.accountNumber = data.accountNumber.padStart(7, '0'); } // 店舗情報を配列に格納 data.stores = Array.from( storeContainer.querySelectorAll('.store-block') ).map(storeBlock => ({ storeName: storeBlock.querySelector('.storeName').value.trim(), storeCategory: storeBlock.querySelector('.storeCategory').value.trim(), storePhone: normalizeDigits(storeBlock.querySelector('.storePhone').value).replace(/[-ー-\s]/g, ''), storePostalCode: normalizeDigits(storeBlock.querySelector('.storePostalCode').value), storePrefecture: storeBlock.querySelector('.storePrefecture').value.trim(), storeCity: storeBlock.querySelector('.storeCity').value.trim(), storeAddress: storeBlock.querySelector('.storeAddress').value.trim() })); data.termsAgreed = form.elements.termsAgreed.checked; data.privacyAgreed = form.elements.privacyAgreed.checked; data.passAgreed = true; data.formStartedAt = formStartedAt.value; return data; } // 入力内容をチェック function validate(data) { const errors = []; const dangerousChars = /[<>"]/; const required = [ 'businessName', 'contactEmail', 'accountType' ]; required.forEach(name => { if (!data[name]) { errors.push(`${labels[name]}を入力してください。`); } }); // 店舗情報の入力チェック data.stores.forEach((store, index) => { const storeNumber = index + 1; if (!store.storeName) { errors.push(`店舗名${storeNumber}を入力してください。`); } else if (store.storeName.length > 80) { errors.push(`店舗名${storeNumber}は80文字以内で入力してください。`); } if (!store.storeCategory) { errors.push(`店舗業態${storeNumber}を選択してください。`); } // 店舗電話番号チェック if (!store.storePhone) { errors.push(`店舗電話番号${storeNumber}を入力してください。`); } else if (!/^\d{10,11}$/.test(store.storePhone)) { errors.push( `店舗電話番号${storeNumber}は数字10桁または11桁で入力してください。` ); } //住所変更対応 if (!store.storePostalCode) { errors.push(`郵便番号${storeNumber}を入力してください。`); } else if (!/^\d{7}$/.test(store.storePostalCode)) { errors.push(`郵便番号${storeNumber}はハイフンなしで数字7桁で入力してください。`); } if (!store.storePrefecture) { errors.push(`都道府県${storeNumber}を入力してください。`); } else if (store.storePrefecture.length > 20) { errors.push(`都道府県${storeNumber}は20文字以内で入力してください。`); } if (!store.storeCity) { errors.push(`市町村${storeNumber}を入力してください。`); } else if (store.storeCity.length > 50) { errors.push(`市町村${storeNumber}は50文字以内で入力してください。`); } if (!store.storeAddress) { errors.push(`番地${storeNumber}を入力してください。`); } else if (store.storeAddress.length > 80) { errors.push(`番地${storeNumber}は80文字以内で入力してください。`); } if (dangerousChars.test(store.storePostalCode)) { errors.push(`郵便番号${storeNumber}に使用できない文字(< > ")が含まれています。`); } if (dangerousChars.test(store.storePrefecture)) { errors.push(`都道府県${storeNumber}に使用できない文字(< > ")が含まれています。`); } if (dangerousChars.test(store.storeCity)) { errors.push(`市町村${storeNumber}に使用できない文字(< > ")が含まれています。`); } if (dangerousChars.test(store.storeAddress)) { errors.push(`番地${storeNumber}に使用できない文字(< > ")が含まれています。`); } //住所変更対応 if (dangerousChars.test(store.storeName)) { errors.push(`店舗名${storeNumber}に使用できない文字(< > ")が含まれています。`); } if (dangerousChars.test(store.storeCategory)) { errors.push(`店舗業態${storeNumber}に使用できない文字(< > ")が含まれています。`); } if (dangerousChars.test(store.storePhone)) { errors.push( `店舗電話番号${storeNumber}に使用できない文字(< > ")が含まれています。` ); } }); // 店舗電話番号の重複チェック const phoneMap = new Map(); data.stores.forEach((store, index) => { if (!store.storePhone) return; const storeNumber = index + 1; if (!phoneMap.has(store.storePhone)) { phoneMap.set(store.storePhone, []); } phoneMap.get(store.storePhone).push(storeNumber); }); phoneMap.forEach((storeNumbers) => { if (storeNumbers.length > 1) { errors.push( `店舗電話番号${storeNumbers.join('と')}が重複しています。電話番号は店舗ごとに別のものを入力してください。` ); } }); // 共通部分の入力チェック if (data.businessName && data.businessName.length > 80) { errors.push('法人名または氏名は80文字以内で入力してください。'); } if (!data.branchCode) { errors.push('口座店番を入力してください。'); } else if (!/^\d+$/.test(data.branchCode)) { errors.push('口座店番は数字で入力してください。'); } if (!data.accountNumber) { errors.push('口座番号を入力してください。'); } else if (!/^\d+$/.test(data.accountNumber)) { errors.push('口座番号は数字で入力してください。'); } if ( data.contactEmail && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(data.contactEmail)) { errors.push('連絡先メールアドレスの形式を確認してください。'); } // 使用禁止文字チェック if (data.businessName && dangerousChars.test(data.businessName)) { errors.push('法人名(個人事業主名)に使用できない文字(< > ")が含まれています。'); } if (data.contactEmail && dangerousChars.test(data.contactEmail)) { errors.push('連絡先メールアドレスに使用できない文字(< > ")が含まれています。'); } if (data.branchCode && dangerousChars.test(data.branchCode)) { errors.push('口座店番に使用できない文字(< > ")が含まれています。'); } if (data.accountType && dangerousChars.test(data.accountType)) { errors.push('口座科目に使用できない文字(< > ")が含まれています。'); } if (data.accountNumber && dangerousChars.test(data.accountNumber)) { errors.push('口座番号に使用できない文字(< > ")が含まれています。'); } if (!data.termsAgreed) { errors.push('さいきょうPAY加盟店規約への同意が必要です。'); } if (!data.privacyAgreed) { errors.push('個人情報の取扱いへの同意が必要です。'); } if (!data.passAgreed) { errors.push('やまぐちパス加盟店規約への同意が必要です。'); } if (data.website) { errors.push('送信内容を確認できませんでした。時間をおいて再度お試しください。'); } return errors; } //エラー出力 function showErrors(errors) { errorBox.replaceChildren(); if (!errors.length) { errorBox.hidden = true; return; } errors.forEach(error => { const div = document.createElement('div'); div.textContent = `・${error}`; errorBox.appendChild(div); }); errorBox.hidden = false; errorBox.scrollIntoView({ behavior: 'smooth', block: 'center' }); } // 確認画面に入力内容を表示 function renderConfirm(data) { confirmList.innerHTML = ''; // 共通項目 Object.keys(labels).forEach(key => { const dt = document.createElement('dt'); dt.textContent = labels[key]; const dd = document.createElement('dd'); dd.textContent = data[key] || ''; confirmList.append(dt, dd); }); // 店舗情報 data.stores.forEach((store, index) => { const storeNumber = index + 1; //住所変更対応 const items = [ [`店舗名${storeNumber}`, store.storeName], [`店舗業態${storeNumber}`, store.storeCategory], [`店舗電話番号${storeNumber}`, store.storePhone], [`郵便番号${storeNumber}`, store.storePostalCode], [`都道府県${storeNumber}`, store.storePrefecture], [`市町村${storeNumber}`, store.storeCity], [`番地${storeNumber}`, store.storeAddress] ]; //住所変更対応 items.forEach(([label, value]) => { const dt = document.createElement('dt'); dt.textContent = label; const dd = document.createElement('dd'); dd.textContent = value || ''; confirmList.append(dt, dd); }); }); } // 確認ボタン処理end // 5.送信ボタン処理 // 送信ボタン押下時 document.getElementById('sendBtn').addEventListener('click', async () => { if (!currentPayload) return; // 二重送信防止のため送信中はボタンを無効化 sendBtn.disabled = true; sendBtn.textContent = '送信中...'; try { let receivedAt = null; // 追加した店舗ごとに電文を作成して送信 for (const store of currentPayload.stores) { const payload = { businessName: currentPayload.businessName, branchCode: currentPayload.branchCode, accountType: currentPayload.accountType, accountNumber: currentPayload.accountNumber, contactEmail: currentPayload.contactEmail, storeName: store.storeName, storeCategory: store.storeCategory, storePhone: store.storePhone, storePostalCode: store.storePostalCode, storePrefecture: store.storePrefecture, storeCity: store.storeCity, storeAddress: store.storeAddress, termsAgreed: currentPayload.termsAgreed, privacyAgreed: currentPayload.privacyAgreed, passAgreed: currentPayload.passAgreed, formStartedAt: currentPayload.formStartedAt, website: currentPayload.website }; const result = await sendToApi(payload); receivedAt = result.receivedAt; } receiptDateEl.textContent = `受付日時:${receivedAt}`; currentPayload = null; initFormStartedAt(); confirmPanel.hidden = true; completePanel.hidden = false; completePanel.scrollIntoView({ behavior: 'smooth' }); } catch (error) { alert( error.message || '送信に失敗しました。時間をおいて再度お試しください。' ); } finally { sendBtn.disabled = false; sendBtn.textContent = '申込内容を送信する'; } }); // 申込内容を送信 async function sendToApi(payload) { if (!API_ENDPOINT) { throw new Error('送信先が未設定です。API_ENDPOINT に URLを設定してください。'); } const response = await fetch(API_ENDPOINT, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), credentials: 'omit', cache: 'no-store' }); if (!response.ok) { throw new Error('申込内容の送信に失敗しました。'); } let body = null; try { body = await response.json(); } catch (error) { console.error('JSON解析失敗:', error); body = null; } return { receivedAt: new Date().toLocaleString() }; } // 送信ボタン処理end // 6.修正ボタン押下時 document.getElementById('backBtn').addEventListener('click', () => { confirmPanel.hidden = true; formPanel.hidden = false; formPanel.scrollIntoView({ behavior: 'smooth' }); }); // 修正ボタン押下時end // 7.リセットボタン押下時 document.getElementById('clearBtn').addEventListener('click', () => { if (confirm('入力内容をクリアしますか?')) { form.reset(); // 住所候補をクリア storeContainer.querySelectorAll('.address-candidates').forEach(candidatesContainer => { candidatesContainer.replaceChildren(); candidatesContainer.hidden = true; }); initFormStartedAt(); showErrors([]); } }); // リセットボタン押下時end // 8 // 8.入力画面へ戻るボタン押下時 document.getElementById('newBtn').addEventListener('click', () => { // 次回も使用する項目の値を保持 const businessName = form.elements.businessName.value; const branchCode = form.elements.branchCode.value; const accountType = form.elements.accountType.value; const accountNumber = form.elements.accountNumber.value; const contactEmail = form.elements.contactEmail.value; // フォームをリセット form.reset(); // 店舗を1店舗分に戻す const storeBlocks = storeContainer.querySelectorAll('.store-block'); storeBlocks.forEach((storeBlock, index) => { if (index > 0) { storeBlock.remove(); } }); // 住所候補をクリア storeContainer.querySelectorAll('.address-candidates').forEach(candidatesContainer => { candidatesContainer.replaceChildren(); candidatesContainer.hidden = true; }); // 保持した項目を再設定 form.elements.businessName.value = businessName; form.elements.branchCode.value = branchCode; form.elements.accountType.value = accountType; form.elements.accountNumber.value = accountNumber; form.elements.contactEmail.value = contactEmail; // 新しい入力開始日時を記録 initFormStartedAt(); // 前回の申込データを破棄 currentPayload = null; // 完了画面を非表示にして入力画面を表示 completePanel.hidden = true; formPanel.hidden = false; formPanel.scrollIntoView({ behavior: 'smooth' }); }); // 入力画面へ戻るボタン押下時end