field-test-plugin.ts 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620
  1. import { LanguageCode } from '@vendure/common/lib/generated-types';
  2. import { Collection, PaymentMethodHandler, PluginCommonModule, Product, VendurePlugin } from '@vendure/core';
  3. /**
  4. * @description
  5. * A comprehensive test payment handler that exercises every type of configurable operation argument
  6. * and UI component available in the dashboard. This handler is intended for development and testing
  7. * purposes only to validate the universal form input system.
  8. *
  9. * Tests all DefaultFormComponentId values:
  10. * - text-form-input, password-form-input, textarea-form-input
  11. * - number-form-input, currency-form-input, boolean-form-input
  12. * - select-form-input, date-form-input
  13. * - rich-text-form-input, json-editor-form-input
  14. *
  15. * Tests all ConfigArgType values:
  16. * - string, int, float, boolean, datetime, ID
  17. * - Both single values and lists
  18. * - Various UI configurations (min, max, step, options, etc.)
  19. */
  20. const comprehensiveTestPaymentHandler = new PaymentMethodHandler({
  21. code: 'comprehensive-test-payment-handler',
  22. description: [
  23. {
  24. languageCode: LanguageCode.en,
  25. value: 'Comprehensive test payment handler with all argument types and UI components',
  26. },
  27. ],
  28. args: {
  29. // === STRING ARGS ===
  30. apiKey: {
  31. type: 'string',
  32. label: [{ languageCode: LanguageCode.en, value: 'API Key' }],
  33. description: [{ languageCode: LanguageCode.en, value: 'Payment gateway API key' }],
  34. ui: { component: 'password-form-input' },
  35. required: true,
  36. },
  37. merchantId: {
  38. type: 'string',
  39. label: [{ languageCode: LanguageCode.en, value: 'Merchant ID' }],
  40. description: [{ languageCode: LanguageCode.en, value: 'Merchant identifier' }],
  41. ui: { component: 'text-form-input' },
  42. required: true,
  43. },
  44. environment: {
  45. type: 'string',
  46. label: [{ languageCode: LanguageCode.en, value: 'Environment' }],
  47. description: [{ languageCode: LanguageCode.en, value: 'Payment environment' }],
  48. ui: {
  49. component: 'select-form-input',
  50. options: [
  51. { value: 'sandbox', label: [{ languageCode: LanguageCode.en, value: 'Sandbox' }] },
  52. { value: 'production', label: [{ languageCode: LanguageCode.en, value: 'Production' }] },
  53. ],
  54. },
  55. defaultValue: 'sandbox',
  56. },
  57. webhookUrl: {
  58. type: 'string',
  59. label: [{ languageCode: LanguageCode.en, value: 'Webhook URL' }],
  60. description: [{ languageCode: LanguageCode.en, value: 'Webhook endpoint URL' }],
  61. ui: { component: 'textarea-form-input' },
  62. },
  63. // === STRING LIST ARGS ===
  64. supportedCurrencies: {
  65. type: 'string',
  66. list: true,
  67. label: [{ languageCode: LanguageCode.en, value: 'Supported Currencies' }],
  68. description: [{ languageCode: LanguageCode.en, value: 'List of supported currency codes' }],
  69. },
  70. allowedCountries: {
  71. type: 'string',
  72. list: true,
  73. label: [{ languageCode: LanguageCode.en, value: 'Allowed Countries' }],
  74. description: [{ languageCode: LanguageCode.en, value: 'Countries where payment is allowed' }],
  75. ui: {
  76. component: 'select-form-input',
  77. options: [
  78. { value: 'US', label: [{ languageCode: LanguageCode.en, value: 'United States' }] },
  79. { value: 'GB', label: [{ languageCode: LanguageCode.en, value: 'United Kingdom' }] },
  80. { value: 'CA', label: [{ languageCode: LanguageCode.en, value: 'Canada' }] },
  81. { value: 'AU', label: [{ languageCode: LanguageCode.en, value: 'Australia' }] },
  82. ],
  83. },
  84. },
  85. // === INTEGER ARGS ===
  86. timeout: {
  87. type: 'int',
  88. label: [{ languageCode: LanguageCode.en, value: 'Timeout (seconds)' }],
  89. description: [{ languageCode: LanguageCode.en, value: 'Payment request timeout' }],
  90. ui: {
  91. component: 'number-form-input',
  92. min: 1,
  93. max: 300,
  94. step: 1,
  95. suffix: 's',
  96. },
  97. defaultValue: 30,
  98. },
  99. maxRetries: {
  100. type: 'int',
  101. label: [{ languageCode: LanguageCode.en, value: 'Max Retries' }],
  102. description: [{ languageCode: LanguageCode.en, value: 'Maximum retry attempts' }],
  103. ui: {
  104. component: 'number-form-input',
  105. min: 0,
  106. max: 10,
  107. step: 1,
  108. },
  109. defaultValue: 3,
  110. },
  111. // === FLOAT ARGS ===
  112. processingFee: {
  113. type: 'float',
  114. label: [{ languageCode: LanguageCode.en, value: 'Processing Fee' }],
  115. description: [{ languageCode: LanguageCode.en, value: 'Processing fee percentage' }],
  116. ui: {
  117. component: 'number-form-input',
  118. min: 0.0,
  119. max: 10.0,
  120. step: 0.01,
  121. suffix: '%',
  122. },
  123. defaultValue: 2.5,
  124. },
  125. exchangeRate: {
  126. type: 'float',
  127. label: [{ languageCode: LanguageCode.en, value: 'Exchange Rate' }],
  128. description: [{ languageCode: LanguageCode.en, value: 'Currency exchange rate' }],
  129. ui: {
  130. component: 'number-form-input',
  131. min: 0.01,
  132. step: 0.0001,
  133. },
  134. defaultValue: 1.0,
  135. },
  136. // === BOOLEAN ARGS ===
  137. enableLogging: {
  138. type: 'boolean',
  139. label: [{ languageCode: LanguageCode.en, value: 'Enable Logging' }],
  140. description: [{ languageCode: LanguageCode.en, value: 'Enable detailed logging' }],
  141. ui: { component: 'boolean-form-input' },
  142. defaultValue: false,
  143. },
  144. requireBillingAddress: {
  145. type: 'boolean',
  146. label: [{ languageCode: LanguageCode.en, value: 'Require Billing Address' }],
  147. description: [{ languageCode: LanguageCode.en, value: 'Require billing address for payments' }],
  148. ui: { component: 'boolean-form-input' },
  149. defaultValue: true,
  150. },
  151. testMode: {
  152. type: 'boolean',
  153. label: [{ languageCode: LanguageCode.en, value: 'Test Mode' }],
  154. description: [{ languageCode: LanguageCode.en, value: 'Enable test mode' }],
  155. ui: { component: 'boolean-form-input' },
  156. defaultValue: true,
  157. },
  158. // === DATETIME ARGS ===
  159. validFrom: {
  160. type: 'datetime',
  161. label: [{ languageCode: LanguageCode.en, value: 'Valid From' }],
  162. description: [{ languageCode: LanguageCode.en, value: 'Payment method valid from date' }],
  163. ui: { component: 'date-form-input' },
  164. },
  165. validUntil: {
  166. type: 'datetime',
  167. label: [{ languageCode: LanguageCode.en, value: 'Valid Until' }],
  168. description: [{ languageCode: LanguageCode.en, value: 'Payment method valid until date' }],
  169. ui: { component: 'date-form-input' },
  170. },
  171. // === ID ARGS ===
  172. partnerId: {
  173. type: 'ID',
  174. label: [{ languageCode: LanguageCode.en, value: 'Partner ID' }],
  175. description: [{ languageCode: LanguageCode.en, value: 'Payment partner identifier' }],
  176. },
  177. vendorId: {
  178. type: 'ID',
  179. label: [{ languageCode: LanguageCode.en, value: 'Vendor ID' }],
  180. description: [{ languageCode: LanguageCode.en, value: 'Payment vendor identifier' }],
  181. },
  182. // === SPECIALIZED UI COMPONENTS ===
  183. baseCurrency: {
  184. type: 'string',
  185. label: [{ languageCode: LanguageCode.en, value: 'Base Currency' }],
  186. description: [{ languageCode: LanguageCode.en, value: 'Base currency for calculations' }],
  187. ui: { component: 'currency-form-input' },
  188. defaultValue: 'USD',
  189. },
  190. termsAndConditions: {
  191. type: 'string',
  192. label: [{ languageCode: LanguageCode.en, value: 'Terms and Conditions' }],
  193. description: [{ languageCode: LanguageCode.en, value: 'Payment terms and conditions' }],
  194. ui: { component: 'rich-text-form-input' },
  195. },
  196. advancedConfig: {
  197. type: 'string',
  198. label: [{ languageCode: LanguageCode.en, value: 'Advanced Configuration' }],
  199. description: [{ languageCode: LanguageCode.en, value: 'Advanced JSON configuration' }],
  200. ui: {
  201. component: 'json-editor-form-input',
  202. height: '200px',
  203. },
  204. defaultValue: '{"webhookRetries": 3, "timeout": 30000}',
  205. },
  206. },
  207. createPayment: async (ctx, order, amount, args, metadata) => {
  208. // Simulate different payment outcomes based on metadata
  209. if (metadata.shouldDecline) {
  210. return {
  211. amount,
  212. state: 'Declined' as const,
  213. metadata: {
  214. errorMessage: 'Test decline simulation',
  215. },
  216. };
  217. } else if (metadata.shouldError) {
  218. return {
  219. amount,
  220. state: 'Error' as const,
  221. errorMessage: 'Test error simulation',
  222. metadata: {
  223. errorMessage: 'Test error simulation',
  224. },
  225. };
  226. } else {
  227. return {
  228. amount,
  229. state: args.testMode ? 'Authorized' : 'Settled',
  230. transactionId: 'test-' + Math.random().toString(36).substr(2, 9),
  231. metadata: {
  232. ...metadata,
  233. processingFee: args.processingFee,
  234. environment: args.environment,
  235. },
  236. };
  237. }
  238. },
  239. settlePayment: async (ctx, order, payment, args) => {
  240. if (payment.metadata.shouldErrorOnSettle) {
  241. return {
  242. success: false,
  243. errorMessage: 'Test settlement error simulation',
  244. };
  245. }
  246. return {
  247. success: true,
  248. metadata: {
  249. settledAt: new Date().toISOString(),
  250. processingFee: args.processingFee,
  251. },
  252. };
  253. },
  254. cancelPayment: async (ctx, order, payment) => {
  255. return {
  256. success: true,
  257. metadata: {
  258. cancellationDate: new Date().toISOString(),
  259. reason: 'Test cancellation',
  260. },
  261. };
  262. },
  263. });
  264. /**
  265. * @description
  266. * FieldTestPlugin provides comprehensive test cases for all custom field types and
  267. * configurable operation argument types supported by Vendure. This plugin is designed
  268. * specifically for development and testing purposes to validate the universal form
  269. * input system in the dashboard.
  270. *
  271. * ## Custom Fields Coverage
  272. * Tests all CustomFieldType values on the Product entity:
  273. * - string (with and without options, lists)
  274. * - localeString (translatable strings)
  275. * - text (long text fields)
  276. * - localeText (translatable long text)
  277. * - int (with min/max/step validation)
  278. * - float (with precision controls)
  279. * - boolean (single and list)
  280. * - datetime (dates and date lists)
  281. * - relation (single and multi-relation)
  282. * - struct (complex objects and lists)
  283. *
  284. * ## Configurable Operation Args Coverage
  285. * Tests all ConfigArgType values and DefaultFormComponentId components:
  286. * - All basic types: string, int, float, boolean, datetime, ID
  287. * - All UI components: text, password, textarea, number, currency, boolean,
  288. * select, date, rich-text, json-editor
  289. * - Advanced features: lists, options, validation, prefixes/suffixes
  290. *
  291. * ## UI Features Tested
  292. * - Tab organization
  293. * - Full-width layouts
  294. * - Readonly fields
  295. * - Field validation (min/max/step)
  296. * - Select options and multi-select
  297. * - List field management
  298. * - Custom UI component integration
  299. *
  300. * ## Usage
  301. * 1. Add this plugin to your dev-config.ts plugins array
  302. * 2. Navigate to any Product detail page to see custom fields
  303. * 3. Go to Settings → Payment Methods → Add "Comprehensive Test Payment Handler"
  304. * to see configurable operation arguments
  305. *
  306. * @docsCategory plugin
  307. * @since 3.4.0
  308. */
  309. @VendurePlugin({
  310. imports: [PluginCommonModule],
  311. configuration: config => {
  312. // Add comprehensive custom fields to Product entity
  313. config.customFields.Product.push(
  314. // === STRING FIELDS ===
  315. {
  316. name: 'infoUrl',
  317. type: 'string',
  318. label: [{ languageCode: LanguageCode.en, value: 'Info URL' }],
  319. description: [{ languageCode: LanguageCode.en, value: 'Product information URL' }],
  320. },
  321. {
  322. name: 'customSku',
  323. type: 'string',
  324. label: [{ languageCode: LanguageCode.en, value: 'Custom SKU' }],
  325. description: [{ languageCode: LanguageCode.en, value: 'Custom SKU for this product' }],
  326. readonly: true,
  327. },
  328. {
  329. name: 'category',
  330. type: 'string',
  331. list: false,
  332. label: [{ languageCode: LanguageCode.en, value: 'Category' }],
  333. description: [{ languageCode: LanguageCode.en, value: 'Product category selection' }],
  334. options: [
  335. {
  336. value: 'electronics',
  337. label: [{ languageCode: LanguageCode.en, value: 'Electronics' }],
  338. },
  339. { value: 'clothing', label: [{ languageCode: LanguageCode.en, value: 'Clothing' }] },
  340. { value: 'books', label: [{ languageCode: LanguageCode.en, value: 'Books' }] },
  341. { value: 'home', label: [{ languageCode: LanguageCode.en, value: 'Home & Garden' }] },
  342. ],
  343. },
  344. {
  345. name: 'tags',
  346. type: 'string',
  347. list: true,
  348. label: [{ languageCode: LanguageCode.en, value: 'Tags' }],
  349. description: [{ languageCode: LanguageCode.en, value: 'Product tags (list)' }],
  350. },
  351. {
  352. name: 'features',
  353. type: 'string',
  354. list: true,
  355. label: [{ languageCode: LanguageCode.en, value: 'Key Features' }],
  356. description: [{ languageCode: LanguageCode.en, value: 'List of product features' }],
  357. options: [
  358. { value: 'wireless', label: [{ languageCode: LanguageCode.en, value: 'Wireless' }] },
  359. { value: 'waterproof', label: [{ languageCode: LanguageCode.en, value: 'Waterproof' }] },
  360. {
  361. value: 'rechargeable',
  362. label: [{ languageCode: LanguageCode.en, value: 'Rechargeable' }],
  363. },
  364. { value: 'portable', label: [{ languageCode: LanguageCode.en, value: 'Portable' }] },
  365. ],
  366. },
  367. // === LOCALE STRING FIELDS ===
  368. {
  369. name: 'shortName',
  370. type: 'localeString',
  371. label: [{ languageCode: LanguageCode.en, value: 'Short Name' }],
  372. description: [{ languageCode: LanguageCode.en, value: 'Short product name (translatable)' }],
  373. },
  374. {
  375. name: 'seoTitle',
  376. type: 'localeString',
  377. label: [{ languageCode: LanguageCode.en, value: 'SEO Title' }],
  378. description: [{ languageCode: LanguageCode.en, value: 'SEO page title (translatable)' }],
  379. ui: { tab: 'SEO' },
  380. },
  381. // === TEXT FIELDS ===
  382. {
  383. name: 'specifications',
  384. type: 'text',
  385. label: [{ languageCode: LanguageCode.en, value: 'Specifications' }],
  386. description: [{ languageCode: LanguageCode.en, value: 'Product specifications (long text)' }],
  387. ui: { fullWidth: true },
  388. },
  389. {
  390. name: 'warrantyInfo',
  391. type: 'localeText',
  392. label: [{ languageCode: LanguageCode.en, value: 'Warranty Information' }],
  393. description: [
  394. { languageCode: LanguageCode.en, value: 'Warranty details (translatable long text)' },
  395. ],
  396. ui: { fullWidth: true, tab: 'Details' },
  397. },
  398. // === BOOLEAN FIELDS ===
  399. {
  400. name: 'downloadable',
  401. type: 'boolean',
  402. label: [{ languageCode: LanguageCode.en, value: 'Downloadable' }],
  403. description: [{ languageCode: LanguageCode.en, value: 'Is this a downloadable product?' }],
  404. },
  405. {
  406. name: 'featured',
  407. type: 'boolean',
  408. label: [{ languageCode: LanguageCode.en, value: 'Featured Product' }],
  409. description: [{ languageCode: LanguageCode.en, value: 'Show on homepage' }],
  410. },
  411. {
  412. name: 'exclusiveOffers',
  413. type: 'boolean',
  414. list: true,
  415. label: [{ languageCode: LanguageCode.en, value: 'Exclusive Offers' }],
  416. description: [{ languageCode: LanguageCode.en, value: 'Multiple boolean values' }],
  417. },
  418. // === INTEGER FIELDS ===
  419. {
  420. name: 'weight',
  421. type: 'int',
  422. label: [{ languageCode: LanguageCode.en, value: 'Weight (grams)' }],
  423. description: [{ languageCode: LanguageCode.en, value: 'Product weight in grams' }],
  424. min: 0,
  425. max: 50000,
  426. step: 10,
  427. },
  428. {
  429. name: 'priority',
  430. type: 'int',
  431. label: [{ languageCode: LanguageCode.en, value: 'Priority' }],
  432. description: [{ languageCode: LanguageCode.en, value: 'Display priority (1-10)' }],
  433. min: 1,
  434. max: 10,
  435. step: 1,
  436. },
  437. {
  438. name: 'dimensions',
  439. type: 'int',
  440. list: true,
  441. label: [{ languageCode: LanguageCode.en, value: 'Dimensions (L×W×H)' }],
  442. description: [{ languageCode: LanguageCode.en, value: 'Product dimensions in cm' }],
  443. min: 0,
  444. max: 1000,
  445. },
  446. // === FLOAT FIELDS ===
  447. {
  448. name: 'rating',
  449. type: 'float',
  450. label: [{ languageCode: LanguageCode.en, value: 'Average Rating' }],
  451. description: [{ languageCode: LanguageCode.en, value: 'Average customer rating' }],
  452. min: 0.0,
  453. max: 5.0,
  454. step: 0.1,
  455. readonly: true,
  456. },
  457. {
  458. name: 'temperature',
  459. type: 'float',
  460. label: [{ languageCode: LanguageCode.en, value: 'Operating Temperature' }],
  461. description: [{ languageCode: LanguageCode.en, value: 'Operating temperature range' }],
  462. min: -40.0,
  463. max: 85.0,
  464. step: 0.5,
  465. },
  466. {
  467. name: 'measurements',
  468. type: 'float',
  469. list: true,
  470. label: [{ languageCode: LanguageCode.en, value: 'Measurements' }],
  471. description: [{ languageCode: LanguageCode.en, value: 'Precise measurements list' }],
  472. step: 0.01,
  473. },
  474. // === DATETIME FIELDS ===
  475. {
  476. name: 'lastUpdated',
  477. type: 'datetime',
  478. label: [{ languageCode: LanguageCode.en, value: 'Last Updated' }],
  479. description: [{ languageCode: LanguageCode.en, value: 'When product was last updated' }],
  480. readonly: true,
  481. },
  482. {
  483. name: 'releaseDate',
  484. type: 'datetime',
  485. label: [{ languageCode: LanguageCode.en, value: 'Release Date' }],
  486. description: [{ languageCode: LanguageCode.en, value: 'Product release date' }],
  487. },
  488. {
  489. name: 'availabilityDates',
  490. type: 'datetime',
  491. list: true,
  492. label: [{ languageCode: LanguageCode.en, value: 'Availability Dates' }],
  493. description: [{ languageCode: LanguageCode.en, value: 'Special availability dates' }],
  494. },
  495. // === RELATION FIELDS ===
  496. {
  497. name: 'brand',
  498. type: 'relation',
  499. entity: Collection,
  500. label: [{ languageCode: LanguageCode.en, value: 'Brand' }],
  501. description: [
  502. { languageCode: LanguageCode.en, value: 'Product brand (collection relation)' },
  503. ],
  504. },
  505. {
  506. name: 'relatedProducts',
  507. type: 'relation',
  508. entity: Product,
  509. list: true,
  510. label: [{ languageCode: LanguageCode.en, value: 'Related Products' }],
  511. description: [{ languageCode: LanguageCode.en, value: 'List of related products' }],
  512. },
  513. {
  514. name: 'manufacturer',
  515. type: 'relation',
  516. entity: Collection,
  517. label: [{ languageCode: LanguageCode.en, value: 'Manufacturer' }],
  518. description: [{ languageCode: LanguageCode.en, value: 'Product manufacturer' }],
  519. ui: { tab: 'Details' },
  520. },
  521. // === STRUCT FIELDS ===
  522. {
  523. name: 'productSpecs',
  524. type: 'struct',
  525. label: [{ languageCode: LanguageCode.en, value: 'Product Specifications' }],
  526. description: [{ languageCode: LanguageCode.en, value: 'Structured product specifications' }],
  527. ui: { fullWidth: true, tab: 'Specifications' },
  528. fields: [
  529. { name: 'cpu', type: 'string' as const },
  530. { name: 'memory', type: 'int' as const },
  531. { name: 'storage', type: 'int' as const },
  532. { name: 'display', type: 'string' as const },
  533. ],
  534. },
  535. {
  536. name: 'variations',
  537. type: 'struct',
  538. list: true,
  539. label: [{ languageCode: LanguageCode.en, value: 'Product Variations' }],
  540. description: [
  541. { languageCode: LanguageCode.en, value: 'List of product variant specifications' },
  542. ],
  543. ui: { fullWidth: true, tab: 'Variants' },
  544. fields: [
  545. { name: 'color', type: 'string' as const },
  546. { name: 'size', type: 'string' as const },
  547. { name: 'price', type: 'float' as const },
  548. { name: 'inStock', type: 'boolean' as const },
  549. ],
  550. },
  551. // === FIELDS WITH CUSTOM UI COMPONENTS (if available) ===
  552. {
  553. name: 'customData',
  554. type: 'string',
  555. label: [{ languageCode: LanguageCode.en, value: 'Custom Data' }],
  556. description: [{ languageCode: LanguageCode.en, value: 'Field with custom UI component' }],
  557. ui: {
  558. component: 'custom-text-input', // This would need to be registered
  559. tab: 'Advanced',
  560. },
  561. },
  562. // === FIELDS WITH TABS ===
  563. {
  564. name: 'seoDescription',
  565. type: 'text',
  566. label: [{ languageCode: LanguageCode.en, value: 'SEO Description' }],
  567. description: [{ languageCode: LanguageCode.en, value: 'SEO meta description' }],
  568. ui: { tab: 'SEO', fullWidth: true },
  569. },
  570. {
  571. name: 'seoKeywords',
  572. type: 'string',
  573. list: true,
  574. label: [{ languageCode: LanguageCode.en, value: 'SEO Keywords' }],
  575. description: [{ languageCode: LanguageCode.en, value: 'SEO keywords' }],
  576. ui: { tab: 'SEO' },
  577. },
  578. {
  579. name: 'technicalNotes',
  580. type: 'text',
  581. label: [{ languageCode: LanguageCode.en, value: 'Technical Notes' }],
  582. description: [{ languageCode: LanguageCode.en, value: 'Internal technical notes' }],
  583. ui: { tab: 'Internal', fullWidth: true },
  584. },
  585. {
  586. name: 'internalCode',
  587. type: 'string',
  588. label: [{ languageCode: LanguageCode.en, value: 'Internal Code' }],
  589. description: [{ languageCode: LanguageCode.en, value: 'Internal tracking code' }],
  590. ui: { tab: 'Internal' },
  591. },
  592. );
  593. // Add comprehensive test payment handler
  594. config.paymentOptions.paymentMethodHandlers.push(comprehensiveTestPaymentHandler);
  595. return config;
  596. },
  597. })
  598. export class FieldTestPlugin {}