shopify-checkout-test

Automatically fill in test data on Shopify's checkout page.

目前為 2024-07-05 提交的版本,檢視 最新版本

  1. // ==UserScript==
  2. // @name shopify-checkout-test
  3. // @namespace https://github.com/pansong291/
  4. // @version 0.0.2
  5. // @description Automatically fill in test data on Shopify's checkout page.
  6. // @description:zh 在 Shopify 的结账页面上自动填充测试数据。
  7. // @author paso
  8. // @license Apache-2.0
  9. // @match *://*.myshopify.com/checkouts/*
  10. // @match *://checkout.shopifycs.com/*
  11. // @icon data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20109.5%20124.5%22%3E%3Cpath%20fill%3D%22%2395BF47%22%20d%3D%22M74.7%2C14.8c0%2C0-1.4%2C0.4-3.7%2C1.1c-0.4-1.3-1-2.8-1.8-4.4c-2.6-5-6.5-7.7-11.1-7.7c0%2C0%2C0%2C0%2C0%2C0c-0.3%2C0-0.6%2C0-1%2C0.1c-0.1-0.2-0.3-0.3-0.4-0.5c-2-2.2-4.6-3.2-7.7-3.1c-6%2C0.2-12%2C4.5-16.8%2C12.2c-3.4%2C5.4-6%2C12.2-6.7%2C17.5c-6.9%2C2.1-11.7%2C3.6-11.8%2C3.7c-3.5%2C1.1-3.6%2C1.2-4%2C4.5C9.1%2C40.7%2C0%2C111.2%2C0%2C111.2l75.6%2C13.1V14.6C75.2%2C14.7%2C74.9%2C14.7%2C74.7%2C14.8zM57.2%2C20.2c-4%2C1.2-8.4%2C2.6-12.7%2C3.9c1.2-4.7%2C3.6-9.4%2C6.4-12.5c1.1-1.1%2C2.6-2.4%2C4.3-3.2C56.9%2C12%2C57.3%2C16.9%2C57.2%2C20.2zM49.1%2C4.3c1.4%2C0%2C2.6%2C0.3%2C3.6%2C0.9c-1.6%2C0.8-3.2%2C2.1-4.7%2C3.6c-3.8%2C4.1-6.7%2C10.5-7.9%2C16.6c-3.6%2C1.1-7.2%2C2.2-10.5%2C3.2C31.7%2C19.1%2C39.8%2C4.6%2C49.1%2C4.3zM37.4%2C59.3c0.4%2C6.4%2C17.3%2C7.8%2C18.3%2C22.9c0.7%2C11.9-6.3%2C20-16.4%2C20.6c-12.2%2C0.8-18.9-6.4-18.9-6.4l2.6-11c0%2C0%2C6.7%2C5.1%2C12.1%2C4.7c3.5-0.2%2C4.8-3.1%2C4.7-5.1c-0.5-8.4-14.3-7.9-15.2-21.7C23.8%2C51.8%2C31.4%2C40.1%2C48.2%2C39c6.5-0.4%2C9.8%2C1.2%2C9.8%2C1.2l-3.8%2C14.4c0%2C0-4.3-2-9.4-1.6C37.4%2C53.5%2C37.3%2C58.2%2C37.4%2C59.3zM61.2%2C19c0-3-0.4-7.3-1.8-10.9c4.6%2C0.9%2C6.8%2C6%2C7.8%2C9.1C65.4%2C17.7%2C63.4%2C18.3%2C61.2%2C19z%22%2F%3E%3Cpath%20fill%3D%22%235E8E3E%22%20d%3D%22M78.1%2C123.9l31.4-7.8c0%2C0-13.5-91.3-13.6-91.9c-0.1-0.6-0.6-1-1.1-1c-0.5%2C0-9.3-0.2-9.3-0.2s-5.4-5.2-7.4-7.2V123.9z%22%2F%3E%3C%2Fsvg%3E
  12. // @grant GM_setValue
  13. // @grant GM_getValue
  14. // @run-at document-start
  15. // @require https://update.greasyfork.org/scripts/473443/1374764/popup-inject.js
  16. // ==/UserScript==
  17.  
  18. ;(function () {
  19. 'use strict'
  20. const namespace = 'paso-shopify-checkout-test'
  21. const qs = document.querySelector.bind(document)
  22. const defData = {
  23. email: '1234567890@qq.com',
  24. countryCode: 'CN',
  25. firstName: '叙乐',
  26. lastName: '欧阳',
  27. address1: '南边大街15号',
  28. address2: 'A幢5楼',
  29. city: '东城区',
  30. zone: 'BJ',
  31. postalCode: '111000',
  32. billingAddress: true,
  33. number: '1',
  34. expiry: '12 / 2099',
  35. verification_value: '123',
  36. name: 'Bogus Gateway',
  37. '居民身份证编号': '110101200012011238'
  38. }
  39. const dataFields = Object.keys(defData)
  40.  
  41. if (window.location.host === 'checkout.shopifycs.com') {
  42. if (window.parent) hookIframe(window.location.pathname.substring(1))
  43. } else {
  44. hookMain()
  45. }
  46.  
  47. function hookIframe(field) {
  48. waitSelector(`#${field}[name=${field}],[name=${field}]`).then((iptElm) => {
  49. setTimeout((elm) => {
  50. const data = getStorageData()
  51. elm.value = data[field] || ''
  52. elm.dispatchEvent(new InputEvent('input'))
  53. }, 500, iptElm)
  54. })
  55. }
  56.  
  57. function hookMain() {
  58. const injectHtml = `
  59. <div class="table monospace" id="div-table"></div>
  60. <div class="flex aj-c">
  61. <button type="button" class="button" id="btn-save">保存</button>
  62. </div>`
  63. const injectStyle = `
  64. <style>
  65. .popup {
  66. gap: 4px;
  67. }
  68. .aj-c {
  69. align-items: center;
  70. justify-content: center;
  71. }
  72. .table {
  73. display: table;
  74. border-spacing: 8px 4px;
  75. }
  76. .table-row {
  77. display: table-row;
  78. }
  79. .table-cell {
  80. display: table-cell;
  81. }
  82. .align-right {
  83. text-align: right;
  84. }
  85. .input[readonly] {
  86. color: #888;
  87. }
  88. </style>`
  89. document.addEventListener('DOMContentLoaded', () => {
  90. window.paso.injectPopup({
  91. namespace,
  92. actionName: 'Checkout Test',
  93. collapse: '80%',
  94. content: injectHtml,
  95. style: injectStyle
  96. }).then((result) => {
  97. const { container, popup } = result.elem
  98. const { createElement } = result.func
  99. const element = {
  100. div_table: popup.querySelector('#div-table'),
  101. btn_save: popup.querySelector('#btn-save')
  102. }
  103. const data = getStorageData()
  104. const inputs = []
  105. for (const field of dataFields) {
  106. const ipt = createElement('input', { class: 'input', name: field })
  107. if (field === 'billingAddress') ipt.setAttribute('readonly', 'readonly')
  108. ipt.value = data[field]
  109. element.div_table.append(createElement('div', { class: 'table-row' }, [
  110. createElement('div', { class: 'table-cell align-right' }, [field]),
  111. createElement('div', { class: 'table-cell' }, [ipt])
  112. ]))
  113. inputs.push(ipt)
  114. }
  115. element.btn_save.addEventListener('click', () => {
  116. const d = {}
  117. inputs.forEach((ipt) => d[ipt.name] = ipt.value)
  118. GM_setValue(namespace, d)
  119. container.classList.remove('open')
  120. })
  121. })
  122. })
  123. waitSelector('#shippingAddressForm > div > div:first-child, #billingAddressForm > div > div:first-child').then((div) => {
  124. const fillData = createDebounce(() => {
  125. const data = getStorageData()
  126. for (const field of dataFields) {
  127. const iptElm = qs(`[name=${field}]`)
  128. if (!iptElm) continue
  129. if (iptElm.tagName.toLowerCase() === 'select') {
  130. iptElm.value = data[field]
  131. iptElm.dispatchEvent(new Event('change'))
  132. } else if (iptElm.type === 'checkbox') {
  133. iptElm.checked = !!data[field]
  134. iptElm.dispatchEvent(new Event('change'))
  135. } else {
  136. iptElm.value = data[field]
  137. iptElm.dispatchEvent(new InputEvent('input'))
  138. }
  139. }
  140. let elm = qs('#basic')
  141. while (elm) {
  142. if (elm.tagName.toLowerCase() === 'section') break
  143. elm = elm.parentElement
  144. }
  145. if (elm) {
  146. const idCardObr = new MutationObserver((mutations) => {
  147. mutations.forEach((m) => {
  148. m.addedNodes?.forEach((n) => {
  149. if (n.tagName.toLowerCase() === 'section') {
  150. const ipt = n.querySelector('[name=居民身份证编号]')
  151. if (ipt) {
  152. ipt.value = data['居民身份证编号']
  153. ipt.dispatchEvent(new InputEvent('input'))
  154. }
  155. }
  156. })
  157. })
  158. })
  159. idCardObr.observe(elm, { childList: true })
  160. }
  161. })
  162. const divObr = new MutationObserver(() => {
  163. divObr.disconnect()
  164. fillData()
  165. })
  166. divObr.observe(div, { childList: true })
  167. fillData()
  168. })
  169. }
  170.  
  171. function createDebounce(func, ms = 500) {
  172. let delayId
  173. return function (...args) {
  174. clearTimeout(delayId)
  175. delayId = setTimeout(() => {
  176. func.apply(this, args)
  177. }, ms)
  178. }
  179. }
  180.  
  181. function getStorageData() {
  182. return GM_getValue(namespace, defData)
  183. }
  184.  
  185. function waitSelector(selector) {
  186. return new Promise((resolve) => {
  187. const loopId = setInterval(() => {
  188. const elm = qs(selector)
  189. if (elm) {
  190. clearInterval(loopId)
  191. resolve(elm)
  192. }
  193. }, 500)
  194. })
  195. }
  196. })()