@fluentcommerce/fc-connect-sdk 0.1.54 → 0.1.55
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/dist/cjs/clients/fluent-client.js +13 -6
- package/dist/cjs/utils/pagination-helpers.js +38 -2
- package/dist/cjs/versori/fluent-versori-client.js +11 -5
- package/dist/esm/clients/fluent-client.js +13 -6
- package/dist/esm/utils/pagination-helpers.js +38 -2
- package/dist/esm/versori/fluent-versori-client.js +11 -5
- package/dist/tsconfig.esm.tsbuildinfo +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/tsconfig.types.tsbuildinfo +1 -1
- package/docs/00-START-HERE/EXPORT-VALIDATION.md +158 -158
- package/docs/00-START-HERE/cli-analyze-source-structure-guide.md +655 -655
- package/docs/00-START-HERE/cli-documentation-index.md +202 -202
- package/docs/00-START-HERE/cli-quick-reference.md +252 -252
- package/docs/00-START-HERE/decision-tree.md +552 -552
- package/docs/00-START-HERE/getting-started.md +1070 -1070
- package/docs/00-START-HERE/mapper-quick-decision-guide.md +235 -235
- package/docs/00-START-HERE/readme.md +237 -237
- package/docs/00-START-HERE/retailerid-configuration.md +404 -404
- package/docs/00-START-HERE/sdk-philosophy.md +794 -794
- package/docs/00-START-HERE/troubleshooting-quick-reference.md +1086 -1086
- package/docs/01-TEMPLATES/faq.md +686 -686
- package/docs/01-TEMPLATES/patterns/pattern-templates-guide.md +68 -68
- package/docs/01-TEMPLATES/patterns/patterns-csv-schema-validation-and-rejection-report.md +233 -233
- package/docs/01-TEMPLATES/patterns/patterns-custom-resolvers.md +407 -407
- package/docs/01-TEMPLATES/patterns/patterns-error-handling-retry.md +511 -511
- package/docs/01-TEMPLATES/patterns/patterns-field-mapping-universal.md +701 -701
- package/docs/01-TEMPLATES/patterns/patterns-large-file-splitting.md +1430 -1430
- package/docs/01-TEMPLATES/patterns/patterns-master-data-etl.md +2399 -2399
- package/docs/01-TEMPLATES/patterns/patterns-pagination-streaming.md +447 -447
- package/docs/01-TEMPLATES/patterns/patterns-state-duplicate-prevention.md +385 -385
- package/docs/01-TEMPLATES/readme.md +957 -957
- package/docs/01-TEMPLATES/standalone/standalone-asn-inbound-processing.md +1209 -1209
- package/docs/01-TEMPLATES/standalone/standalone-graphql-query-export.md +1140 -1140
- package/docs/01-TEMPLATES/standalone/standalone-graphql-to-parquet-partitioned-s3.md +432 -432
- package/docs/01-TEMPLATES/standalone/standalone-multi-channel-inventory-sync.md +1185 -1185
- package/docs/01-TEMPLATES/standalone/standalone-multi-source-aggregation.md +1462 -1462
- package/docs/01-TEMPLATES/standalone/standalone-s3-csv-batch-api.md +1390 -1390
- package/docs/01-TEMPLATES/standalone/standalone-s3-csv-inventory-to-batch.md +330 -330
- package/docs/01-TEMPLATES/standalone/standalone-scripts-guide.md +87 -87
- package/docs/01-TEMPLATES/standalone/standalone-sftp-xml-graphql.md +1444 -1444
- package/docs/01-TEMPLATES/standalone/standalone-webhook-payload-processing.md +688 -688
- package/docs/01-TEMPLATES/versori/business-examples/business-examples-dropship-order-routing.md +193 -193
- package/docs/01-TEMPLATES/versori/business-examples/business-examples-graphql-parquet-extraction.md +518 -518
- package/docs/01-TEMPLATES/versori/business-examples/business-examples-inter-location-transfers.md +2162 -2162
- package/docs/01-TEMPLATES/versori/business-examples/business-examples-pre-order-allocation.md +2226 -2226
- package/docs/01-TEMPLATES/versori/business-examples/business-scenarios-guide.md +87 -87
- package/docs/01-TEMPLATES/versori/patterns/versori-patterns-connection-validation-pattern.md +656 -656
- package/docs/01-TEMPLATES/versori/patterns/versori-patterns-dual-workflow-connector.md +835 -835
- package/docs/01-TEMPLATES/versori/patterns/versori-patterns-guide.md +108 -108
- package/docs/01-TEMPLATES/versori/patterns/versori-patterns-kv-state-management.md +1533 -1533
- package/docs/01-TEMPLATES/versori/patterns/versori-patterns-xml-response-patterns.md +1160 -1160
- package/docs/01-TEMPLATES/versori/versori-platform-guide.md +201 -201
- package/docs/01-TEMPLATES/versori/webhooks/template-webhook-asn-purchase-order.md +1906 -1906
- package/docs/01-TEMPLATES/versori/webhooks/template-webhook-dropship-routing.md +1074 -1074
- package/docs/01-TEMPLATES/versori/webhooks/template-webhook-flash-sale-reserve.md +1395 -1395
- package/docs/01-TEMPLATES/versori/webhooks/template-webhook-generic-xml-order.md +888 -888
- package/docs/01-TEMPLATES/versori/webhooks/template-webhook-payment-gateway-integration.md +2478 -2478
- package/docs/01-TEMPLATES/versori/webhooks/template-webhook-rma-returns-comprehensive.md +2240 -2240
- package/docs/01-TEMPLATES/versori/webhooks/template-webhook-xml-order-ingestion.md +2029 -2029
- package/docs/01-TEMPLATES/versori/webhooks/webhook-templates-guide.md +140 -140
- package/docs/01-TEMPLATES/versori/workflows/_examples/sample-data/inventory-mapping.json +20 -20
- package/docs/01-TEMPLATES/versori/workflows/_examples/sample-data/products_2025-01-22.csv +11 -11
- package/docs/01-TEMPLATES/versori/workflows/_examples/sample-data/sample-data-guide.md +34 -34
- package/docs/01-TEMPLATES/versori/workflows/_examples/workflow-examples-guide.md +36 -36
- package/docs/01-TEMPLATES/versori/workflows/extraction/extraction-modes-guide.md +1038 -1038
- package/docs/01-TEMPLATES/versori/workflows/extraction/extraction-workflows-guide.md +138 -138
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/graphql-extraction-guide.md +63 -63
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-fulfillments-to-sftp-csv.md +2062 -2062
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-fulfillments-to-sftp-xml.md +2294 -2294
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-inventory-positions-to-s3-csv.md +2461 -2461
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-inventory-positions-to-sftp-xml.md +2529 -2529
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-inventory-quantities-to-s3-csv.md +2464 -2464
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-inventory-quantities-to-s3-json.md +1959 -1959
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-orders-to-s3-csv.md +1953 -1953
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-orders-to-sftp-xml.md +2541 -2541
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-products-to-s3-json.md +2384 -2384
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-products-to-sftp-xml.md +2445 -2445
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-virtual-positions-to-s3-csv.md +2355 -2355
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-virtual-positions-to-s3-json.md +2042 -2042
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-virtual-positions-to-sftp-xml.md +2726 -2726
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/batch-api-guide.md +206 -206
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-cycle-count-reconciliation.md +2030 -2030
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-multi-channel-inventory-sync.md +1882 -1882
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-s3-csv-inventory-batch.md +2827 -2827
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-s3-json-inventory-batch.md +1952 -1952
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-s3-xml-inventory-batch.md +3289 -3289
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-sftp-csv-inventory-batch.md +3064 -3064
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-sftp-json-inventory-batch.md +3238 -3238
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-sftp-xml-inventory-batch.md +2977 -2977
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/event-api-guide.md +321 -321
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-payload-json-order-cancel-event.md +959 -959
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-payload-xml-order-cancel-event.md +1170 -1170
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-s3-csv-product-event.md +2312 -2312
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-s3-json-product-event.md +2999 -2999
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-s3-parquet-product-event.md +2836 -2836
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-s3-xml-product-event.md +2395 -2395
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-sftp-csv-product-event.md +2295 -2295
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-sftp-json-product-event.md +2602 -2602
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-sftp-parquet-product-event.md +2589 -2589
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-sftp-xml-product-event.md +3578 -3578
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/graphql-mutations-guide.md +93 -93
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-payload-json-order-update-graphql.md +1260 -1260
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-payload-xml-order-update-graphql.md +1472 -1472
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-s3-csv-control-graphql.md +2417 -2417
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-s3-csv-location-graphql.md +2811 -2811
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-s3-csv-price-graphql.md +2619 -2619
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-s3-json-location-graphql.md +2807 -2807
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-s3-xml-location-graphql.md +2373 -2373
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-sftp-csv-control-graphql.md +2740 -2740
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-sftp-csv-location-graphql.md +2760 -2760
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-sftp-json-location-graphql.md +1710 -1710
- package/docs/01-TEMPLATES/versori/workflows/ingestion/ingestion-workflows-guide.md +136 -136
- package/docs/01-TEMPLATES/versori/workflows/rubix-webhooks/rubix-webhooks-guide.md +520 -520
- package/docs/01-TEMPLATES/versori/workflows/rubix-webhooks/template-webhook-rubix-fulfilment-to-sftp-xml-inline.md +1418 -1418
- package/docs/01-TEMPLATES/versori/workflows/rubix-webhooks/template-webhook-rubix-fulfilment-to-sftp-xml-universal-mapper.md +1785 -1785
- package/docs/01-TEMPLATES/versori/workflows/rubix-webhooks/template-webhook-rubix-order-attribute-update.md +824 -824
- package/docs/01-TEMPLATES/versori/workflows/workflows-overview-guide.md +646 -646
- package/docs/02-CORE-GUIDES/advanced-services/advanced-services-batch-archival.md +724 -724
- package/docs/02-CORE-GUIDES/advanced-services/advanced-services-job-tracker.md +627 -627
- package/docs/02-CORE-GUIDES/advanced-services/advanced-services-partial-batch-recovery.md +561 -561
- package/docs/02-CORE-GUIDES/advanced-services/advanced-services-quick-reference.md +367 -367
- package/docs/02-CORE-GUIDES/advanced-services/advanced-services-readme.md +407 -407
- package/docs/02-CORE-GUIDES/advanced-services/readme.md +49 -49
- package/docs/02-CORE-GUIDES/api-reference/api-reference-quick-reference.md +548 -548
- package/docs/02-CORE-GUIDES/api-reference/event-api-input-output-reference.md +702 -1171
- package/docs/02-CORE-GUIDES/api-reference/examples/client-initialization.ts +286 -286
- package/docs/02-CORE-GUIDES/api-reference/graphql-error-classification.md +337 -337
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-01-client-api.md +399 -520
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-03-authentication.md +199 -199
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-04-graphql-mapping.md +925 -925
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-05-services.md +1198 -1198
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-06-data-sources.md +1083 -1083
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-07-parsers.md +1097 -1097
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-08-pagination.md +513 -513
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-08-types.md +545 -597
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-09-error-handling.md +527 -527
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-09-webhook-validation.md +514 -514
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-10-extraction.md +557 -557
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-10-utilities.md +412 -412
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-11-cli-tools.md +423 -423
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-11-error-handling.md +716 -716
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-12-analyze-source-structure.md +518 -518
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-12-partial-responses.md +212 -212
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-12-testing.md +300 -300
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-13-resolver-builder.md +322 -322
- package/docs/02-CORE-GUIDES/api-reference/readme.md +279 -279
- package/docs/02-CORE-GUIDES/auto-pagination/auto-pagination-quick-reference.md +351 -351
- package/docs/02-CORE-GUIDES/auto-pagination/auto-pagination-readme.md +277 -277
- package/docs/02-CORE-GUIDES/auto-pagination/examples/auto-pagination-readme.md +178 -178
- package/docs/02-CORE-GUIDES/auto-pagination/examples/common-patterns.ts +351 -351
- package/docs/02-CORE-GUIDES/auto-pagination/examples/paginate-products.ts +384 -384
- package/docs/02-CORE-GUIDES/auto-pagination/examples/paginate-virtual-positions.ts +308 -308
- package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-01-foundations.md +470 -470
- package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-02-quick-start.md +713 -713
- package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-03-configuration.md +754 -754
- package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-04-advanced-patterns.md +732 -732
- package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-05-sdk-integration.md +847 -847
- package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-06-troubleshooting.md +359 -359
- package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-07-api-reference.md +462 -462
- package/docs/02-CORE-GUIDES/auto-pagination/readme.md +54 -54
- package/docs/02-CORE-GUIDES/data-sources/data-sources-file-operations-error-handling.md +1487 -1487
- package/docs/02-CORE-GUIDES/data-sources/data-sources-quick-reference.md +836 -836
- package/docs/02-CORE-GUIDES/data-sources/data-sources-readme.md +276 -276
- package/docs/02-CORE-GUIDES/data-sources/data-sources-sftp-credential-access-security.md +553 -553
- package/docs/02-CORE-GUIDES/data-sources/examples/common-patterns.ts +409 -409
- package/docs/02-CORE-GUIDES/data-sources/examples/data-sources-readme.md +178 -178
- package/docs/02-CORE-GUIDES/data-sources/examples/s3-operations.ts +308 -308
- package/docs/02-CORE-GUIDES/data-sources/examples/sftp-operations.ts +371 -371
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-01-foundations.md +735 -735
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-02-s3-operations.md +1302 -1302
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-03-sftp-operations.md +1379 -1379
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-04-file-patterns.md +941 -941
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-05-advanced-topics.md +813 -813
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-06-integration-patterns.md +486 -486
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-07-troubleshooting.md +387 -387
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-08-api-reference.md +417 -417
- package/docs/02-CORE-GUIDES/data-sources/readme.md +77 -77
- package/docs/02-CORE-GUIDES/error-handling-guide.md +936 -936
- package/docs/02-CORE-GUIDES/extraction/examples/02-core-guides-extraction-readme.md +116 -116
- package/docs/02-CORE-GUIDES/extraction/examples/common-patterns.ts +428 -428
- package/docs/02-CORE-GUIDES/extraction/examples/extract-inventory-basic.ts +187 -187
- package/docs/02-CORE-GUIDES/extraction/extraction-quick-reference.md +596 -596
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-01-foundations.md +514 -514
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-02-basic-extraction.md +823 -823
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-03-parquet-processing.md +507 -507
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-04-data-enrichment.md +546 -546
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-05-transformation.md +494 -494
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-06-export-formats.md +458 -458
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-06-performance.md +138 -138
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-07-api-reference.md +148 -148
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-07-optimization.md +692 -692
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-08-extraction-orchestrator.md +1008 -1008
- package/docs/02-CORE-GUIDES/extraction/readme.md +151 -151
- package/docs/02-CORE-GUIDES/ingestion/examples/_simple-kv-store.ts +40 -40
- package/docs/02-CORE-GUIDES/ingestion/examples/error-recovery.ts +728 -728
- package/docs/02-CORE-GUIDES/ingestion/examples/event-driven.ts +501 -501
- package/docs/02-CORE-GUIDES/ingestion/examples/local-file-ingestion.ts +88 -88
- package/docs/02-CORE-GUIDES/ingestion/examples/parquet-ingestion.ts +117 -117
- package/docs/02-CORE-GUIDES/ingestion/examples/performance-optimized.ts +647 -647
- package/docs/02-CORE-GUIDES/ingestion/examples/s3-csv-ingestion.ts +169 -169
- package/docs/02-CORE-GUIDES/ingestion/examples/sftp-csv-ingestion.ts +134 -134
- package/docs/02-CORE-GUIDES/ingestion/ingestion-quick-reference.md +546 -546
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-01-introduction.md +626 -626
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-02-quick-start.md +658 -658
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-03-data-sources.md +1052 -1052
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-04-field-mapping.md +763 -763
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-05-advanced-parsers.md +676 -676
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-06-batch-api.md +1295 -1295
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-07-api-reference.md +138 -138
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-07-state-management.md +1037 -1037
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-08-performance-optimization.md +1349 -1349
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-09-best-practices.md +1893 -1893
- package/docs/02-CORE-GUIDES/ingestion/readme.md +160 -160
- package/docs/02-CORE-GUIDES/logging-guide.md +585 -585
- package/docs/02-CORE-GUIDES/mapping/error-handling-patterns.md +401 -401
- package/docs/02-CORE-GUIDES/mapping/examples/02-core-guides-mapping-readme.md +128 -128
- package/docs/02-CORE-GUIDES/mapping/examples/common-patterns.ts +273 -273
- package/docs/02-CORE-GUIDES/mapping/examples/csv-location-ingestion.json +36 -36
- package/docs/02-CORE-GUIDES/mapping/examples/csv-mapping.ts +242 -242
- package/docs/02-CORE-GUIDES/mapping/examples/graphql-to-parquet-extraction.json +36 -36
- package/docs/02-CORE-GUIDES/mapping/examples/json-mapping.ts +213 -213
- package/docs/02-CORE-GUIDES/mapping/examples/json-product-to-mutation.json +48 -48
- package/docs/02-CORE-GUIDES/mapping/examples/xml-mapping.ts +291 -291
- package/docs/02-CORE-GUIDES/mapping/examples/xml-order-to-mutation.json +45 -45
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/graphql-mutation-mapping-quick-reference.md +463 -463
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/graphql-mutation-mapping-readme.md +227 -227
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-01-introduction.md +222 -222
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-02-quick-start.md +351 -351
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-03-schema-validation.md +569 -569
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-04-mapping-patterns.md +471 -471
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-05-configuration-reference.md +611 -611
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-06-advanced-xpath.md +148 -148
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-06-path-syntax.md +464 -464
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-07-api-reference.md +94 -94
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-07-array-handling.md +307 -307
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-08-custom-resolvers.md +544 -544
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-09-advanced-patterns.md +427 -427
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-10-hooks-and-variables.md +336 -336
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-11-error-handling.md +488 -488
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-12-arguments-vs-nodes.md +383 -383
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-13-best-practices.md +477 -477
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/readme.md +62 -62
- package/docs/02-CORE-GUIDES/mapping/mapping-format-decision-tree.md +480 -480
- package/docs/02-CORE-GUIDES/mapping/mapping-graphql-alias-batching-guide.md +820 -820
- package/docs/02-CORE-GUIDES/mapping/mapping-javascript-objects.md +2369 -2369
- package/docs/02-CORE-GUIDES/mapping/mapping-mapper-comparison-guide.md +682 -682
- package/docs/02-CORE-GUIDES/mapping/modules/02-core-guides-mapping-07-api-reference.md +1327 -1327
- package/docs/02-CORE-GUIDES/mapping/modules/02-core-guides-mapping-08-error-handling.md +1142 -1142
- package/docs/02-CORE-GUIDES/mapping/modules/mapping-04-use-cases.md +891 -891
- package/docs/02-CORE-GUIDES/mapping/modules/mapping-06-helpers-resolvers.md +1126 -1126
- package/docs/02-CORE-GUIDES/mapping/modules/mapping-06-sdk-resolvers.md +199 -199
- package/docs/02-CORE-GUIDES/mapping/modules/mapping-07-api-reference.md +1319 -1319
- package/docs/02-CORE-GUIDES/mapping/readme.md +178 -178
- package/docs/02-CORE-GUIDES/mapping/resolver-registration.md +410 -410
- package/docs/02-CORE-GUIDES/mapping/resolvers/examples/common-patterns.ts +226 -226
- package/docs/02-CORE-GUIDES/mapping/resolvers/examples/custom-resolvers.ts +227 -227
- package/docs/02-CORE-GUIDES/mapping/resolvers/examples/sdk-resolvers-usage.ts +203 -203
- package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-readme.md +274 -274
- package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-api-reference.md +679 -679
- package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-cookbook.md +826 -826
- package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-guide.md +1330 -1330
- package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-helpers-reference.md +1437 -1437
- package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-parameters-reference.md +553 -553
- package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-troubleshooting.md +854 -854
- package/docs/02-CORE-GUIDES/mapping/resolvers/readme.md +75 -75
- package/docs/02-CORE-GUIDES/parsers/examples/02-core-guides-parsers-readme.md +161 -161
- package/docs/02-CORE-GUIDES/parsers/examples/csv-parser-examples.ts +110 -110
- package/docs/02-CORE-GUIDES/parsers/examples/json-parser-examples.ts +33 -33
- package/docs/02-CORE-GUIDES/parsers/examples/parquet-parser-examples.ts +47 -47
- package/docs/02-CORE-GUIDES/parsers/examples/xml-parser-examples.ts +38 -38
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-01-foundations.md +355 -355
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-02-csv-parser.md +772 -772
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-03-json-parser.md +789 -789
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-04-xml-parser.md +857 -857
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-05-parquet-parser.md +603 -603
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-06-integration-patterns.md +702 -702
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-06-streaming.md +121 -121
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-07-api-reference.md +89 -89
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-07-troubleshooting.md +727 -727
- package/docs/02-CORE-GUIDES/parsers/parsers-quick-reference.md +482 -482
- package/docs/02-CORE-GUIDES/parsers/parsers-readme.md +258 -258
- package/docs/02-CORE-GUIDES/parsers/readme.md +65 -65
- package/docs/02-CORE-GUIDES/readme.md +194 -194
- package/docs/02-CORE-GUIDES/webhook-validation/examples/basic-validation.ts +108 -108
- package/docs/02-CORE-GUIDES/webhook-validation/examples/common-patterns.ts +316 -316
- package/docs/02-CORE-GUIDES/webhook-validation/examples/webhook-validation-readme.md +61 -61
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-01-foundations.md +440 -440
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-02-quick-start.md +525 -525
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-03-versori-integration.md +741 -741
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-04-platform-integration.md +629 -629
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-05-configuration.md +535 -535
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-06-error-handling.md +611 -611
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-06-troubleshooting.md +124 -124
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-07-api-reference.md +511 -511
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-08-rubix-webhooks.md +590 -590
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-09-rubix-event-vs-http-call.md +432 -432
- package/docs/02-CORE-GUIDES/webhook-validation/readme.md +239 -239
- package/docs/02-CORE-GUIDES/webhook-validation/webhook-validation-quick-reference.md +392 -392
- package/docs/03-PATTERN-GUIDES/connector-scenarios/connector-scenarios-quick-reference.md +498 -498
- package/docs/03-PATTERN-GUIDES/connector-scenarios/connector-scenarios-readme.md +313 -313
- package/docs/03-PATTERN-GUIDES/connector-scenarios/examples/common-patterns.ts +612 -612
- package/docs/03-PATTERN-GUIDES/connector-scenarios/examples/connector-scenarios-readme.md +253 -253
- package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-01-foundations.md +452 -452
- package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-02-simple-scenarios.md +681 -681
- package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-03-intermediate-scenarios.md +637 -637
- package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-04-advanced-scenarios.md +650 -650
- package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-05-bidirectional-sync.md +233 -233
- package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-06-production-patterns.md +442 -442
- package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-07-reference.md +445 -445
- package/docs/03-PATTERN-GUIDES/connector-scenarios/readme.md +31 -31
- package/docs/03-PATTERN-GUIDES/enterprise-integration-patterns.md +1528 -1528
- package/docs/03-PATTERN-GUIDES/error-handling/comprehensive-error-handling-guide.md +1437 -1437
- package/docs/03-PATTERN-GUIDES/error-handling/error-handling-quick-reference.md +390 -390
- package/docs/03-PATTERN-GUIDES/error-handling/examples/common-patterns.ts +438 -438
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-01-foundations.md +362 -362
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-02-error-types.md +850 -850
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-03-utf8-handling.md +456 -456
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-04-error-scenarios.md +658 -658
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-05-calling-patterns.md +671 -671
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-06-retry-strategies.md +1034 -1034
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-07-monitoring.md +653 -653
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-08-api-reference.md +847 -847
- package/docs/03-PATTERN-GUIDES/error-handling/readme.md +36 -36
- package/docs/03-PATTERN-GUIDES/examples/__tests__/readme.md +40 -40
- package/docs/03-PATTERN-GUIDES/examples/__tests__/resolver-examples.test.js +282 -282
- package/docs/03-PATTERN-GUIDES/examples/test-data/03-pattern-guides-readme.md +110 -110
- package/docs/03-PATTERN-GUIDES/examples/test-data/canonical-inventory.json +123 -123
- package/docs/03-PATTERN-GUIDES/examples/test-data/canonical-order.json +171 -171
- package/docs/03-PATTERN-GUIDES/examples/test-data/readme.md +28 -28
- package/docs/03-PATTERN-GUIDES/extraction/extraction-readme.md +15 -15
- package/docs/03-PATTERN-GUIDES/extraction/readme.md +25 -25
- package/docs/03-PATTERN-GUIDES/file-operations/examples/common-patterns.ts +407 -407
- package/docs/03-PATTERN-GUIDES/file-operations/examples/file-operations-readme.md +142 -142
- package/docs/03-PATTERN-GUIDES/file-operations/file-operations-quick-reference.md +462 -462
- package/docs/03-PATTERN-GUIDES/file-operations/file-operations-readme.md +379 -379
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-01-foundations.md +430 -430
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-02-quick-start.md +484 -484
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-03-s3-operations.md +507 -507
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-04-sftp-operations.md +963 -963
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-05-streaming-performance.md +503 -503
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-06-archive-patterns.md +386 -386
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-06-error-handling.md +117 -117
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-07-api-reference.md +78 -78
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-07-testing-troubleshooting.md +567 -567
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-08-api-reference.md +1055 -1055
- package/docs/03-PATTERN-GUIDES/file-operations/readme.md +32 -32
- package/docs/03-PATTERN-GUIDES/ingestion/ingestion-readme.md +15 -15
- package/docs/03-PATTERN-GUIDES/ingestion/readme.md +25 -25
- package/docs/03-PATTERN-GUIDES/integration-patterns/examples/batch-processing.ts +130 -130
- package/docs/03-PATTERN-GUIDES/integration-patterns/examples/common-patterns.ts +360 -360
- package/docs/03-PATTERN-GUIDES/integration-patterns/examples/delta-sync.ts +130 -130
- package/docs/03-PATTERN-GUIDES/integration-patterns/examples/integration-patterns-readme.md +100 -100
- package/docs/03-PATTERN-GUIDES/integration-patterns/examples/real-time-webhook.ts +398 -398
- package/docs/03-PATTERN-GUIDES/integration-patterns/integration-patterns-quick-reference.md +962 -962
- package/docs/03-PATTERN-GUIDES/integration-patterns/integration-patterns-readme.md +134 -134
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-01-real-time-processing.md +991 -991
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-02-batch-processing.md +1547 -1547
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-03-delta-sync.md +1108 -1108
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-04-webhook-patterns.md +1181 -1181
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-05-error-handling.md +1061 -1061
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-06-advanced-integration-services.md +1547 -1547
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-06-performance.md +109 -109
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-07-api-reference.md +34 -34
- package/docs/03-PATTERN-GUIDES/integration-patterns/readme.md +30 -30
- package/docs/03-PATTERN-GUIDES/logging-minimal-mode.md +128 -128
- package/docs/03-PATTERN-GUIDES/multiple-connections/examples/common-patterns.ts +380 -380
- package/docs/03-PATTERN-GUIDES/multiple-connections/examples/multiple-connections-readme.md +139 -139
- package/docs/03-PATTERN-GUIDES/multiple-connections/examples/parallel-root-connections.ts +149 -149
- package/docs/03-PATTERN-GUIDES/multiple-connections/examples/real-world-scenarios.ts +405 -405
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-01-foundations.md +378 -378
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-02-quick-start.md +566 -566
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-03-targeting-connections.md +659 -659
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-04-parallel-queries.md +656 -656
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-05-best-practices.md +624 -624
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-06-api-reference.md +824 -824
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-06-versori.md +119 -119
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-07-api-reference.md +87 -87
- package/docs/03-PATTERN-GUIDES/multiple-connections/multiple-connections-quick-reference.md +353 -353
- package/docs/03-PATTERN-GUIDES/multiple-connections/multiple-connections-readme.md +270 -270
- package/docs/03-PATTERN-GUIDES/multiple-connections/readme.md +30 -30
- package/docs/03-PATTERN-GUIDES/pagination/pagination-readme.md +14 -14
- package/docs/03-PATTERN-GUIDES/pagination/readme.md +24 -24
- package/docs/03-PATTERN-GUIDES/parquet/examples/common-patterns.ts +180 -180
- package/docs/03-PATTERN-GUIDES/parquet/examples/read-parquet.ts +48 -48
- package/docs/03-PATTERN-GUIDES/parquet/examples/write-parquet.ts +65 -65
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-01-introduction.md +393 -393
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-02-quick-start.md +572 -572
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-03-reading-parquet.md +525 -525
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-04-writing-parquet.md +554 -554
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-05-graphql-extraction.md +405 -405
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-06-performance.md +104 -104
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-06-s3-integration.md +511 -511
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-07-api-reference.md +90 -90
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-07-performance-optimization.md +525 -525
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-08-best-practices.md +712 -712
- package/docs/03-PATTERN-GUIDES/parquet/parquet-quick-reference.md +683 -683
- package/docs/03-PATTERN-GUIDES/parquet/parquet-readme.md +248 -248
- package/docs/03-PATTERN-GUIDES/parquet/readme.md +32 -32
- package/docs/03-PATTERN-GUIDES/parsers/parsers-readme.md +12 -12
- package/docs/03-PATTERN-GUIDES/parsers/readme.md +24 -24
- package/docs/03-PATTERN-GUIDES/readme.md +159 -159
- package/docs/03-PATTERN-GUIDES/webhooks/readme.md +24 -24
- package/docs/03-PATTERN-GUIDES/webhooks/webhooks-readme.md +8 -8
- package/docs/04-REFERENCE/architecture/architecture-01-overview.md +427 -427
- package/docs/04-REFERENCE/architecture/architecture-02-client-architecture.md +424 -424
- package/docs/04-REFERENCE/architecture/architecture-03-data-flow.md +690 -690
- package/docs/04-REFERENCE/architecture/architecture-04-service-layer.md +834 -834
- package/docs/04-REFERENCE/architecture/architecture-05-integration-architecture.md +655 -655
- package/docs/04-REFERENCE/architecture/architecture-06-state-management.md +653 -653
- package/docs/04-REFERENCE/architecture/architecture-adding-new-data-sources.md +686 -686
- package/docs/04-REFERENCE/architecture/readme.md +279 -279
- package/docs/04-REFERENCE/platforms/deno/readme.md +117 -117
- package/docs/04-REFERENCE/platforms/nodejs/readme.md +146 -146
- package/docs/04-REFERENCE/platforms/readme.md +135 -135
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-01-introduction.md +398 -398
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-02-quick-start.md +560 -560
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-03-authentication.md +757 -757
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-04-workflows.md +2476 -2476
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-05-connections.md +1167 -1167
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-06-kv-storage.md +990 -990
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-06-state-management.md +121 -121
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-07-api-reference.md +68 -68
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-07-deployment.md +731 -731
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-08-best-practices.md +1111 -1111
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-09-signature-reference.md +766 -766
- package/docs/04-REFERENCE/platforms/versori/platforms-versori-readme.md +299 -299
- package/docs/04-REFERENCE/platforms/versori/platforms-versori-s3-sftp-configuration-guide.md +1425 -1425
- package/docs/04-REFERENCE/platforms/versori/platforms-versori-webhook-api-key-security.md +816 -816
- package/docs/04-REFERENCE/platforms/versori/platforms-versori-webhook-connection-security.md +681 -681
- package/docs/04-REFERENCE/platforms/versori/platforms-versori-workflow-task-types.md +708 -708
- package/docs/04-REFERENCE/platforms/versori/readme.md +108 -108
- package/docs/04-REFERENCE/readme.md +148 -148
- package/docs/04-REFERENCE/resolver-signature/examples/advanced-resolvers.ts +482 -482
- package/docs/04-REFERENCE/resolver-signature/examples/async-resolvers.ts +496 -496
- package/docs/04-REFERENCE/resolver-signature/examples/basic-resolvers.ts +343 -343
- package/docs/04-REFERENCE/resolver-signature/examples/resolver-signature-readme.md +188 -188
- package/docs/04-REFERENCE/resolver-signature/examples/testing-resolvers.ts +463 -463
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-01-foundations.md +286 -286
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-02-parameter-reference.md +643 -643
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-03-basic-examples.md +521 -521
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-04-advanced-patterns.md +739 -739
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-05-sdk-resolvers.md +531 -531
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-06-migration-guide.md +650 -650
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-06-testing.md +125 -125
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-07-api-reference.md +794 -794
- package/docs/04-REFERENCE/resolver-signature/readme.md +64 -64
- package/docs/04-REFERENCE/resolver-signature/resolver-signature-quick-reference.md +270 -270
- package/docs/04-REFERENCE/resolver-signature/resolver-signature-readme.md +351 -351
- package/docs/04-REFERENCE/schema/fluent-commerce-schema.json +764 -764
- package/docs/04-REFERENCE/schema/readme.md +141 -141
- package/docs/04-REFERENCE/testing/examples/04-reference-testing-readme.md +158 -158
- package/docs/04-REFERENCE/testing/examples/fluent-testing.ts +62 -62
- package/docs/04-REFERENCE/testing/examples/health-check.ts +155 -155
- package/docs/04-REFERENCE/testing/examples/integration-test.ts +119 -119
- package/docs/04-REFERENCE/testing/examples/performance-test.ts +183 -183
- package/docs/04-REFERENCE/testing/examples/s3-testing.ts +127 -127
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-01-foundations.md +267 -267
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-02-s3-testing.md +599 -599
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-03-fluent-testing.md +589 -589
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-04-integration-testing.md +699 -699
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-05-debugging.md +478 -478
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-06-cicd-integration.md +463 -463
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-06-preflight-validation.md +131 -131
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-07-best-practices.md +499 -499
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-07-coverage-ci.md +165 -165
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-08-api-reference.md +634 -634
- package/docs/04-REFERENCE/testing/readme.md +86 -86
- package/docs/04-REFERENCE/testing/testing-quick-reference.md +667 -667
- package/docs/04-REFERENCE/testing/testing-readme.md +286 -286
- package/docs/04-REFERENCE/troubleshooting/readme.md +144 -144
- package/docs/04-REFERENCE/troubleshooting/troubleshooting-deno-sftp-compatibility.md +392 -392
- package/docs/template-loading-matrix.md +242 -242
- package/package.json +5 -3
- package/docs/02-CORE-GUIDES/api-reference/cli-profile-integration.md +0 -377
|
@@ -1,1472 +1,1472 @@
|
|
|
1
|
-
---
|
|
2
|
-
template_id: tpl-ingest-payload-xml-to-order-update-graphql
|
|
3
|
-
canonical_filename: template-ingestion-payload-xml-order-update-graphql.md
|
|
4
|
-
sdk_version: latest
|
|
5
|
-
runtime: versori
|
|
6
|
-
direction: ingestion
|
|
7
|
-
source: payload-xml
|
|
8
|
-
destination: fluent-graphql
|
|
9
|
-
entity: order
|
|
10
|
-
format: xml
|
|
11
|
-
logging: versori
|
|
12
|
-
status: stable
|
|
13
|
-
features:
|
|
14
|
-
- direct-payload-processing
|
|
15
|
-
- xml-parsing
|
|
16
|
-
- graphql-mutations
|
|
17
|
-
- order-query-before-update
|
|
18
|
-
- attribute-updates
|
|
19
|
-
---
|
|
20
|
-
|
|
21
|
-
# Template: Ingestion - Payload XML to Order Update GraphQL
|
|
22
|
-
|
|
23
|
-
**SDK Version:** @fluentcommerce/fc-connect-sdk@latest
|
|
24
|
-
**Last Updated:** 2025-01-24
|
|
25
|
-
**Deployment Target:** Versori Platform
|
|
26
|
-
|
|
27
|
-
---
|
|
28
|
-
|
|
29
|
-
## 📋 Implementation Prompt
|
|
30
|
-
|
|
31
|
-
```
|
|
32
|
-
I need a Versori webhook ingestion that:
|
|
33
|
-
|
|
34
|
-
1) Receives XML payload directly in webhook request body with orderRef and attributes
|
|
35
|
-
2) Parses XML to extract order reference and attributes (handles Versori $ format for attributes)
|
|
36
|
-
3) Queries Fluent Commerce to get order ID by order reference
|
|
37
|
-
4) Updates order attributes using updateOrder GraphQL mutation
|
|
38
|
-
5) Returns success/error response
|
|
39
|
-
6) Uses native Versori log from context
|
|
40
|
-
|
|
41
|
-
Use the loaded docs to fill in SDK specifics and best practices.
|
|
42
|
-
Keep the structure identical to the template; only adapt where needed.
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
---
|
|
46
|
-
|
|
47
|
-
## 📋 Template Overview
|
|
48
|
-
|
|
49
|
-
This connector runs on the Versori platform. It receives XML payloads directly via webhook with order reference and attributes, queries Fluent Commerce to get the order ID, and updates order attributes using GraphQL mutations. Most operational settings (Fluent account/connection) are configured via activation variables.
|
|
50
|
-
|
|
51
|
-
### What This Template Does
|
|
52
|
-
|
|
53
|
-
```
|
|
54
|
-
┌────────────────────────────────────────────────────────────────┐
|
|
55
|
-
│ INGESTION WORKFLOW │
|
|
56
|
-
└────────────────────────────────────────────────────────────────┘
|
|
57
|
-
|
|
58
|
-
1. TRIGGER
|
|
59
|
-
└─ Webhook: HTTP POST endpoint receives XML payload
|
|
60
|
-
|
|
61
|
-
2. RECEIVE PAYLOAD
|
|
62
|
-
├─ Extract XML from request body (ctx.data or ctx.request)
|
|
63
|
-
├─ Validate payload structure
|
|
64
|
-
└─ Log incoming request
|
|
65
|
-
|
|
66
|
-
3. PARSE XML
|
|
67
|
-
├─ Parse XML payload (Versori auto-parses with $ format)
|
|
68
|
-
├─ Extract order reference (handles $ for attributes, _ for text)
|
|
69
|
-
├─ Extract attributes array
|
|
70
|
-
└─ Validate required fields
|
|
71
|
-
|
|
72
|
-
4. QUERY ORDER
|
|
73
|
-
├─ Query Fluent Commerce: order(ref: orderRef)
|
|
74
|
-
├─ Get order ID and current state
|
|
75
|
-
├─ Validate order exists
|
|
76
|
-
└─ Log order details
|
|
77
|
-
|
|
78
|
-
5. BUILD UPDATE MUTATION
|
|
79
|
-
├─ Construct UpdateOrderInput with ref
|
|
80
|
-
├─ Map attributes to AttributeInput format
|
|
81
|
-
├─ Preserve existing attributes (optional merge)
|
|
82
|
-
└─ Build GraphQL mutation variables
|
|
83
|
-
|
|
84
|
-
6. EXECUTE MUTATION
|
|
85
|
-
├─ Execute updateOrder GraphQL mutation
|
|
86
|
-
├─ Handle success/error responses
|
|
87
|
-
└─ Log mutation result
|
|
88
|
-
|
|
89
|
-
7. RETURN RESPONSE
|
|
90
|
-
├─ Return success with order details
|
|
91
|
-
└─ Return error with details if failed
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
### Key Features
|
|
95
|
-
|
|
96
|
-
- **Direct Payload Processing**: Receives XML payload directly in webhook request body
|
|
97
|
-
- **Versori XML Parsing**: Handles Versori auto-parsed XML format ($ for attributes, _ for text)
|
|
98
|
-
- **Order Query First**: Queries order by ref to get ID and validate existence
|
|
99
|
-
- **GraphQL Mutation**: Uses updateOrder mutation to update attributes
|
|
100
|
-
- **Attribute Mapping**: Converts XML attributes to Fluent AttributeInput format
|
|
101
|
-
- **Error Handling**: Comprehensive error handling with detailed logging
|
|
102
|
-
- **Native Versori Logging**: Uses Versori log from context
|
|
103
|
-
|
|
104
|
-
### 📦 Package Information
|
|
105
|
-
|
|
106
|
-
**SDK:** [@fluentcommerce/fc-connect-sdk](https://www.npmjs.com/package/@fluentcommerce/fc-connect-sdk) `latest`
|
|
107
|
-
|
|
108
|
-
```bash
|
|
109
|
-
npm install @fluentcommerce/fc-connect-sdk@latest
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
---
|
|
113
|
-
|
|
114
|
-
**Templates are designed for direct deployment; customize via activation variables.**
|
|
115
|
-
|
|
116
|
-
---
|
|
117
|
-
|
|
118
|
-
## 📦 SDK Imports (Verified - Versori Optimized)
|
|
119
|
-
|
|
120
|
-
```typescript
|
|
121
|
-
// ✅ VERIFIED IMPORTS - These match actual SDK exports
|
|
122
|
-
import { Buffer } from 'node:buffer'; // Required for Versori/Deno runtime
|
|
123
|
-
import {
|
|
124
|
-
createClient, // Universal client factory
|
|
125
|
-
XMLParserService, // XML parsing utility
|
|
126
|
-
} from '@fluentcommerce/fc-connect-sdk';
|
|
127
|
-
|
|
128
|
-
import type { FluentClient } from '@fluentcommerce/fc-connect-sdk';
|
|
129
|
-
|
|
130
|
-
// Versori platform imports
|
|
131
|
-
import { webhook, http } from '@versori/run';
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
**Note:** All imports are from actual SDK exports - this code compiles and runs as-is.
|
|
135
|
-
|
|
136
|
-
**⚠️ CRITICAL - Buffer Import Required:**
|
|
137
|
-
|
|
138
|
-
The `Buffer` import from `node:buffer` is **required** for Versori/Deno runtime compatibility:
|
|
139
|
-
- Versori and Deno do NOT have `Buffer` as a global (unlike Node.js)
|
|
140
|
-
- Without this import, code will crash with `ReferenceError: Buffer is not defined`
|
|
141
|
-
- Always include this import in Versori templates, even if not directly used in the template code
|
|
142
|
-
- Required for SDK internal operations and any Buffer usage in your code
|
|
143
|
-
|
|
144
|
-
**✅ VERSORI PLATFORM - Use Native Logs:**
|
|
145
|
-
|
|
146
|
-
- Use `log` from context: `const { log } = ctx;`
|
|
147
|
-
- Native Versori logs are simpler and automatically integrated with platform monitoring
|
|
148
|
-
|
|
149
|
-
**✅ VERSORI XML PARSING:**
|
|
150
|
-
|
|
151
|
-
Versori automatically parses XML payloads when `Content-Type: application/xml` is set:
|
|
152
|
-
- Attributes accessed via `$` (e.g., `element.$?.attributeName`)
|
|
153
|
-
- Text content accessed via `_` (e.g., `element._`)
|
|
154
|
-
- Nested elements accessed directly (e.g., `element.childElement`)
|
|
155
|
-
|
|
156
|
-
---
|
|
157
|
-
|
|
158
|
-
## 🔐 Fluent Commerce Connection Setup
|
|
159
|
-
|
|
160
|
-
**✅ BEST PRACTICE:** Store Fluent Commerce credentials in a Versori connection object:
|
|
161
|
-
|
|
162
|
-
**Connection Configuration:**
|
|
163
|
-
|
|
164
|
-
1. **Create Connection in Versori:**
|
|
165
|
-
- Name: `fluent_commerce`
|
|
166
|
-
- Type: HTTP Basic Auth or OAuth2
|
|
167
|
-
- Credentials: Fluent Commerce API credentials
|
|
168
|
-
|
|
169
|
-
2. **Connection Variables:**
|
|
170
|
-
- `FLUENT_BASE_URL` - Fluent Commerce API base URL (e.g., `https://api.fluentcommerce.com`)
|
|
171
|
-
- `FLUENT_CLIENT_ID` - OAuth client ID (if using OAuth2)
|
|
172
|
-
- `FLUENT_CLIENT_SECRET` - OAuth client secret (if using OAuth2)
|
|
173
|
-
- `FLUENT_USERNAME` - Username (if using Basic Auth)
|
|
174
|
-
- `FLUENT_PASSWORD` - Password (if using Basic Auth)
|
|
175
|
-
|
|
176
|
-
**Benefits:**
|
|
177
|
-
- ✅ Credentials stored securely in Versori vault
|
|
178
|
-
- ✅ Connection can be reused across workflows
|
|
179
|
-
- ✅ No sensitive data in activation variables
|
|
180
|
-
- ✅ Easier credential rotation
|
|
181
|
-
|
|
182
|
-
---
|
|
183
|
-
|
|
184
|
-
## 📄 Expected XML Payload Format
|
|
185
|
-
|
|
186
|
-
The webhook accepts **flat XML payloads**. All child elements except `orderRef` are automatically treated as order attributes.
|
|
187
|
-
|
|
188
|
-
### ✅ PRIMARY FORMAT: Flat Structure (Recommended)
|
|
189
|
-
|
|
190
|
-
**Simple format - just child elements, no nested attributes structure:**
|
|
191
|
-
|
|
192
|
-
```xml
|
|
193
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
|
194
|
-
<orderUpdate>
|
|
195
|
-
<orderRef>ORD-12345</orderRef>
|
|
196
|
-
<externalOrderId>EXT-123456789</externalOrderId>
|
|
197
|
-
<customerNotes>Please leave package at front door</customerNotes>
|
|
198
|
-
<priority>high</priority>
|
|
199
|
-
<tags>rush,gift</tags>
|
|
200
|
-
</orderUpdate>
|
|
201
|
-
```
|
|
202
|
-
|
|
203
|
-
**Type Auto-Detection:**
|
|
204
|
-
- Numbers → `INTEGER` or `FLOAT` (auto-detected)
|
|
205
|
-
- "true"/"false" → `BOOLEAN` (auto-detected)
|
|
206
|
-
- Strings → `STRING` (default)
|
|
207
|
-
|
|
208
|
-
**cURL Example:**
|
|
209
|
-
```bash
|
|
210
|
-
curl -X POST https://{workspace}.versori.run/order-update \
|
|
211
|
-
-H "Content-Type: application/xml" \
|
|
212
|
-
-H "X-API-Key: your-api-key" \
|
|
213
|
-
-d '<?xml version="1.0" encoding="UTF-8"?><orderUpdate><orderRef>ORD-12345</orderRef><externalOrderId>EXT-123456789</externalOrderId><customerNotes>Please leave package at front door</customerNotes><priority>high</priority></orderUpdate>'
|
|
214
|
-
```
|
|
215
|
-
|
|
216
|
-
### Complete Sample Payloads
|
|
217
|
-
|
|
218
|
-
**Example 1: External System Reference Update**
|
|
219
|
-
```xml
|
|
220
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
|
221
|
-
<orderUpdate>
|
|
222
|
-
<orderRef>ORD-0017326966182</orderRef>
|
|
223
|
-
<externalOrderId>EXT-123456789</externalOrderId>
|
|
224
|
-
<sourceSystem>ERP-SAP</sourceSystem>
|
|
225
|
-
<integrationId>INT-987654321</integrationId>
|
|
226
|
-
</orderUpdate>
|
|
227
|
-
```
|
|
228
|
-
|
|
229
|
-
**Example 2: Customer Notes and Business Flags**
|
|
230
|
-
```xml
|
|
231
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
|
232
|
-
<orderUpdate>
|
|
233
|
-
<orderRef>ORD-0017326966182</orderRef>
|
|
234
|
-
<customerNotes>Please leave package at front door</customerNotes>
|
|
235
|
-
<priority>high</priority>
|
|
236
|
-
<tags>rush,gift</tags>
|
|
237
|
-
<specialHandling>true</specialHandling>
|
|
238
|
-
</orderUpdate>
|
|
239
|
-
```
|
|
240
|
-
|
|
241
|
-
**Note:** Metadata fields (`updateReason`, `updatedBy`, `timestamp`) are optional and excluded from attributes. All other child elements are treated as order attributes.
|
|
242
|
-
|
|
243
|
-
**Versori XML Parsing Format (CRITICAL):**
|
|
244
|
-
|
|
245
|
-
When Versori receives XML with `Content-Type: application/xml`, it **automatically parses** the XML into a JavaScript object. You do NOT need to manually parse XML - Versori handles it automatically.
|
|
246
|
-
|
|
247
|
-
**Access Patterns:**
|
|
248
|
-
- **Attributes**: `element.$?.attributeName` (e.g., `<orderUpdate orderRef="ORD-12345"/>` → `orderUpdate.$?.orderRef`)
|
|
249
|
-
- **Text content**: `element._` or direct property access (e.g., `<orderRef>ORD-12345</orderRef>` → `orderRef._` or `orderRef`)
|
|
250
|
-
- **Nested elements**: Direct property access (e.g., `<orderUpdate><orderRef>...</orderRef></orderUpdate>` → `orderUpdate.orderRef`)
|
|
251
|
-
|
|
252
|
-
**Examples:**
|
|
253
|
-
|
|
254
|
-
```typescript
|
|
255
|
-
// XML: <orderUpdate orderRef="ORD-12345"/>
|
|
256
|
-
// Versori auto-parsed: { orderUpdate: { $: { orderRef: "ORD-12345" } } }
|
|
257
|
-
const orderRef = parsed.orderUpdate?.$?.orderRef; // ✅ "ORD-12345"
|
|
258
|
-
|
|
259
|
-
// XML: <orderRef>ORD-12345</orderRef>
|
|
260
|
-
// Versori auto-parsed: { orderRef: { _: "ORD-12345" } } OR { orderRef: "ORD-12345" }
|
|
261
|
-
const orderRef = parsed.orderRef?._ || parsed.orderRef; // ✅ "ORD-12345"
|
|
262
|
-
|
|
263
|
-
// XML: <attribute name="status" value="processing"/>
|
|
264
|
-
// Versori auto-parsed: { attribute: { $: { name: "status", value: "processing" } } }
|
|
265
|
-
const attrName = parsed.attribute?.$?.name; // ✅ "status"
|
|
266
|
-
const attrValue = parsed.attribute?.$?.value; // ✅ "processing"
|
|
267
|
-
```
|
|
268
|
-
|
|
269
|
-
**Key Point:** Versori automatically parses XML when `Content-Type: application/xml` is set. Your webhook receives `ctx.data` as a pre-parsed JavaScript object - no manual XML parsing needed!
|
|
270
|
-
|
|
271
|
-
---
|
|
272
|
-
|
|
273
|
-
## GraphQL Mutations
|
|
274
|
-
|
|
275
|
-
### Query Order by Reference
|
|
276
|
-
|
|
277
|
-
```graphql
|
|
278
|
-
query GetOrder($ref: String!) {
|
|
279
|
-
order(ref: $ref) {
|
|
280
|
-
id
|
|
281
|
-
ref
|
|
282
|
-
status
|
|
283
|
-
attributes {
|
|
284
|
-
name
|
|
285
|
-
type
|
|
286
|
-
value
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
```
|
|
291
|
-
|
|
292
|
-
### Update Order Mutation
|
|
293
|
-
|
|
294
|
-
```graphql
|
|
295
|
-
mutation UpdateOrder($input: UpdateOrderInput!) {
|
|
296
|
-
updateOrder(input: $input) {
|
|
297
|
-
id
|
|
298
|
-
ref
|
|
299
|
-
status
|
|
300
|
-
attributes {
|
|
301
|
-
name
|
|
302
|
-
type
|
|
303
|
-
value
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
```
|
|
308
|
-
|
|
309
|
-
**UpdateOrderInput Structure:**
|
|
310
|
-
- `id` (ID!) - Order ID (required) - Must use Fluent internal ID, not reference
|
|
311
|
-
- `attributes` (Array<AttributeInput>) - Attributes to update
|
|
312
|
-
- `status` (String) - Optional status update
|
|
313
|
-
- Other optional fields as per schema
|
|
314
|
-
|
|
315
|
-
**Note:** UpdateOrderInput requires `id: ID!` (the Fluent internal ID), not `ref`. This is why the template queries the order first to get the ID.
|
|
316
|
-
|
|
317
|
-
**AttributeInput Structure:**
|
|
318
|
-
- `name` (String!) - Attribute name
|
|
319
|
-
- `type` (String!) - Attribute type (STRING, INTEGER, FLOAT, BOOLEAN, JSON, DATE)
|
|
320
|
-
- `value` (String!) - Attribute value (stringified for non-STRING types)
|
|
321
|
-
|
|
322
|
-
---
|
|
323
|
-
|
|
324
|
-
## 🔧 Complete Production Code
|
|
325
|
-
|
|
326
|
-
### Versori Workflows Structure
|
|
327
|
-
|
|
328
|
-
**Key Concept**: Versori workflows are organized by **trigger type** at the first level, then by **specific workflow** with descriptive file names.
|
|
329
|
-
|
|
330
|
-
**Trigger Types:**
|
|
331
|
-
- **`webhook()`** → HTTP-based triggers (event-driven) - Creates HTTP endpoints
|
|
332
|
-
|
|
333
|
-
**Execution Steps (chained to triggers):**
|
|
334
|
-
- **`http()`** → External API calls (chained from webhook)
|
|
335
|
-
|
|
336
|
-
### Recommended Project Structure
|
|
337
|
-
|
|
338
|
-
```
|
|
339
|
-
order-update-webhook/
|
|
340
|
-
├── index.ts # Entry point - exports all workflows
|
|
341
|
-
└── src/
|
|
342
|
-
├── workflows/
|
|
343
|
-
│ └── webhook/
|
|
344
|
-
│ └── order-update.ts # Webhook: Order update handler
|
|
345
|
-
│
|
|
346
|
-
├── services/
|
|
347
|
-
│ └── order-update.service.ts # Shared orchestration logic
|
|
348
|
-
│
|
|
349
|
-
├── utils/
|
|
350
|
-
│ └── response-status.utils.ts # FC status code mapping utilities
|
|
351
|
-
│
|
|
352
|
-
└── types/
|
|
353
|
-
└── order-update.types.ts # Type definitions
|
|
354
|
-
```
|
|
355
|
-
|
|
356
|
-
**Benefits:**
|
|
357
|
-
- ✅ Clear structure (webhook handlers in `webhook/`)
|
|
358
|
-
- ✅ Descriptive file names (easy to browse and understand)
|
|
359
|
-
- ✅ Reusable code in `services/` (DRY principle)
|
|
360
|
-
- ✅ Centralized type definitions
|
|
361
|
-
- ✅ Utility functions for consistent error handling (FC status codes)
|
|
362
|
-
|
|
363
|
-
---
|
|
364
|
-
|
|
365
|
-
## Workflow Files
|
|
366
|
-
|
|
367
|
-
### 1. Webhook Workflow (`src/workflows/webhook/order-update.ts`)
|
|
368
|
-
|
|
369
|
-
**Purpose**: Handle order update requests via webhook
|
|
370
|
-
**Trigger**: HTTP POST
|
|
371
|
-
**Endpoint**: `POST https://{workspace}.versori.run/order-update`
|
|
372
|
-
**Use Cases**: Order attribute updates from external systems, status changes
|
|
373
|
-
|
|
374
|
-
```typescript
|
|
375
|
-
import { webhook, http } from '@versori/run';
|
|
376
|
-
import { executeOrderUpdate } from '../../services/order-update.service';
|
|
377
|
-
import { mapErrorToFCStatus } from '../../utils/response-status.utils';
|
|
378
|
-
|
|
379
|
-
/**
|
|
380
|
-
* Webhook: Order Update Handler
|
|
381
|
-
*
|
|
382
|
-
* Endpoint: POST https://{workspace}.versori.run/order-update
|
|
383
|
-
* Request body: XML with orderRef and flat attribute elements
|
|
384
|
-
* Content-Type: application/xml
|
|
385
|
-
*
|
|
386
|
-
* Pattern: webhook().then(http()) - needs Fluent API access
|
|
387
|
-
* Uses shared service: order-update.service.ts
|
|
388
|
-
*
|
|
389
|
-
* SECURITY: Authentication handled via connection parameter
|
|
390
|
-
* No manual API key validation needed - Versori manages this via connection auth
|
|
391
|
-
*/
|
|
392
|
-
export const orderUpdateWebhook = webhook('order-update', {
|
|
393
|
-
response: { mode: 'sync' }, // ✅ Sync mode: response sent when handler returns
|
|
394
|
-
connection: 'order-update-webhook', // Versori validates API key
|
|
395
|
-
}).then(
|
|
396
|
-
http('process-order-update', { connection: 'fluent_commerce' }, async ctx => {
|
|
397
|
-
const { log, data } = ctx;
|
|
398
|
-
|
|
399
|
-
log.info('🚀 [WEBHOOK] Order update request received', {
|
|
400
|
-
timestamp: new Date().toISOString(),
|
|
401
|
-
hasPayload: !!data,
|
|
402
|
-
contentType: ctx.request?.headers?.get('content-type'),
|
|
403
|
-
});
|
|
404
|
-
|
|
405
|
-
try {
|
|
406
|
-
// Reuse shared orchestration logic
|
|
407
|
-
const result = await executeOrderUpdate(ctx);
|
|
408
|
-
|
|
409
|
-
log.info('✅ [WEBHOOK] Order update completed successfully', {
|
|
410
|
-
orderRef: result.orderRef,
|
|
411
|
-
orderId: result.orderId,
|
|
412
|
-
attributesUpdated: result.attributesUpdated,
|
|
413
|
-
});
|
|
414
|
-
|
|
415
|
-
// Return response with FC200 status code
|
|
416
|
-
return {
|
|
417
|
-
success: true,
|
|
418
|
-
statusCode: result.statusCode,
|
|
419
|
-
orderRef: result.orderRef,
|
|
420
|
-
orderId: result.orderId,
|
|
421
|
-
attributesUpdated: result.attributesUpdated,
|
|
422
|
-
message: result.message || 'Order attributes updated successfully',
|
|
423
|
-
};
|
|
424
|
-
} catch (e: any) {
|
|
425
|
-
log.error('❌ [WEBHOOK] Order update failed', {
|
|
426
|
-
message: e?.message,
|
|
427
|
-
stack: e?.stack,
|
|
428
|
-
});
|
|
429
|
-
|
|
430
|
-
// Map error to FC status code
|
|
431
|
-
const fcStatus = mapErrorToFCStatus(e);
|
|
432
|
-
|
|
433
|
-
return {
|
|
434
|
-
success: false,
|
|
435
|
-
statusCode: fcStatus.statusCode,
|
|
436
|
-
retryable: fcStatus.retryable,
|
|
437
|
-
error: fcStatus.message,
|
|
438
|
-
details: e?.message || 'Unknown error occurred',
|
|
439
|
-
};
|
|
440
|
-
}
|
|
441
|
-
})
|
|
442
|
-
);
|
|
443
|
-
```
|
|
444
|
-
|
|
445
|
-
---
|
|
446
|
-
|
|
447
|
-
### 2. Entry Point (`index.ts`)
|
|
448
|
-
|
|
449
|
-
**Purpose**: Register all workflows with Versori platform
|
|
450
|
-
|
|
451
|
-
```typescript
|
|
452
|
-
/**
|
|
453
|
-
* Entry Point - Registers all workflows with Versori platform
|
|
454
|
-
*
|
|
455
|
-
* Versori automatically discovers and registers exported workflows
|
|
456
|
-
*
|
|
457
|
-
* File Structure:
|
|
458
|
-
* - src/workflows/webhook/ → HTTP-based triggers (webhooks)
|
|
459
|
-
* - src/services/ → Shared service logic (reusable across workflows)
|
|
460
|
-
*/
|
|
461
|
-
|
|
462
|
-
// Import webhook workflows
|
|
463
|
-
import { orderUpdateWebhook } from './src/workflows/webhook/order-update';
|
|
464
|
-
|
|
465
|
-
// Register all workflows with Versori platform
|
|
466
|
-
export {
|
|
467
|
-
// Webhooks (HTTP-based triggers)
|
|
468
|
-
orderUpdateWebhook,
|
|
469
|
-
};
|
|
470
|
-
```
|
|
471
|
-
|
|
472
|
-
**What Gets Exposed:**
|
|
473
|
-
- ✅ `orderUpdateWebhook` → `https://{workspace}.versori.run/order-update`
|
|
474
|
-
|
|
475
|
-
---
|
|
476
|
-
|
|
477
|
-
## 3. Type Definitions (`src/types/order-update.types.ts`)
|
|
478
|
-
|
|
479
|
-
```typescript
|
|
480
|
-
/**
|
|
481
|
-
* Type Definitions for Order Update Ingestion
|
|
482
|
-
*
|
|
483
|
-
* Centralized type definitions for order update workflow
|
|
484
|
-
*/
|
|
485
|
-
|
|
486
|
-
/**
|
|
487
|
-
* Versori XML parsed format
|
|
488
|
-
* Attributes accessed via $, text content via _
|
|
489
|
-
*/
|
|
490
|
-
export interface VersoriXMLParsed {
|
|
491
|
-
[key: string]: unknown;
|
|
492
|
-
$?: Record<string, unknown>; // Attributes
|
|
493
|
-
_?: string; // Text content
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
/**
|
|
497
|
-
* Order update request payload (XML parsed format)
|
|
498
|
-
*/
|
|
499
|
-
export interface OrderUpdatePayload {
|
|
500
|
-
orderUpdate?: {
|
|
501
|
-
orderRef?: VersoriXMLParsed | string;
|
|
502
|
-
attributes?: {
|
|
503
|
-
attribute?: VersoriXMLParsed | Array<VersoriXMLParsed>;
|
|
504
|
-
};
|
|
505
|
-
$?: {
|
|
506
|
-
orderRef?: string;
|
|
507
|
-
};
|
|
508
|
-
[key: string]: VersoriXMLParsed | string | unknown; // Flat attributes
|
|
509
|
-
};
|
|
510
|
-
order?: {
|
|
511
|
-
ref?: VersoriXMLParsed | string;
|
|
512
|
-
$?: {
|
|
513
|
-
ref?: string;
|
|
514
|
-
};
|
|
515
|
-
};
|
|
516
|
-
[key: string]: unknown; // Allow additional fields
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
/**
|
|
520
|
-
* Fluent AttributeInput format
|
|
521
|
-
*/
|
|
522
|
-
export interface AttributeInput {
|
|
523
|
-
name: string;
|
|
524
|
-
type: 'STRING' | 'INTEGER' | 'FLOAT' | 'BOOLEAN' | 'JSON' | 'DATE';
|
|
525
|
-
value: string;
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
/**
|
|
529
|
-
* Order query result
|
|
530
|
-
*/
|
|
531
|
-
export interface OrderQueryResult {
|
|
532
|
-
id: string;
|
|
533
|
-
ref: string;
|
|
534
|
-
status?: string;
|
|
535
|
-
attributes?: Array<{
|
|
536
|
-
name: string;
|
|
537
|
-
type: string;
|
|
538
|
-
value: string;
|
|
539
|
-
}>;
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
/**
|
|
543
|
-
* Order update result with FC status code
|
|
544
|
-
*/
|
|
545
|
-
export interface OrderUpdateResult {
|
|
546
|
-
success: boolean;
|
|
547
|
-
statusCode: 'FC200' | 'FC400' | 'FC409' | 'FC504';
|
|
548
|
-
orderRef: string;
|
|
549
|
-
orderId: string;
|
|
550
|
-
attributesUpdated: number;
|
|
551
|
-
message?: string;
|
|
552
|
-
error?: string;
|
|
553
|
-
retryable?: boolean;
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
/**
|
|
557
|
-
* Versori Context Interface
|
|
558
|
-
* Represents the Versori runtime context passed to workflow functions
|
|
559
|
-
*/
|
|
560
|
-
export interface VersoriContext {
|
|
561
|
-
log: {
|
|
562
|
-
info: (message: string, data?: Record<string, unknown>) => void;
|
|
563
|
-
warn: (message: string, data?: Record<string, unknown>) => void;
|
|
564
|
-
error: (message: string, data?: Record<string, unknown>) => void;
|
|
565
|
-
debug?: (message: string, data?: Record<string, unknown>) => void;
|
|
566
|
-
};
|
|
567
|
-
data?: unknown;
|
|
568
|
-
request?: Request;
|
|
569
|
-
activation: {
|
|
570
|
-
getVariable: (name: string) => string | undefined;
|
|
571
|
-
connections?: Record<string, unknown>;
|
|
572
|
-
};
|
|
573
|
-
connections?: Record<string, unknown>;
|
|
574
|
-
}
|
|
575
|
-
```
|
|
576
|
-
|
|
577
|
-
---
|
|
578
|
-
|
|
579
|
-
## 4. Utility: Response Status Code Mapper (`src/utils/response-status.utils.ts`)
|
|
580
|
-
|
|
581
|
-
```typescript
|
|
582
|
-
/**
|
|
583
|
-
* Response Status Code Utility
|
|
584
|
-
*
|
|
585
|
-
* Maps errors to Fluent Commerce (FC) status codes for consistent API responses
|
|
586
|
-
*/
|
|
587
|
-
|
|
588
|
-
/**
|
|
589
|
-
* FC Status Codes:
|
|
590
|
-
* - FC200: OK (Successfully - No further action needed)
|
|
591
|
-
* - FC400: Bad Request (Non-retryable)
|
|
592
|
-
* - FC409: Duplicate Request, Entity already exists (Non-retryable)
|
|
593
|
-
* - FC504: Timeout (Retryable)
|
|
594
|
-
*/
|
|
595
|
-
|
|
596
|
-
export type FCStatusCode = 'FC200' | 'FC400' | 'FC409' | 'FC504';
|
|
597
|
-
|
|
598
|
-
export interface FCStatusMapping {
|
|
599
|
-
statusCode: FCStatusCode;
|
|
600
|
-
retryable: boolean;
|
|
601
|
-
message: string;
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
/**
|
|
605
|
-
* Map error to FC status code based on error message and type
|
|
606
|
-
*/
|
|
607
|
-
export function mapErrorToFCStatus(error: unknown): FCStatusMapping {
|
|
608
|
-
const errorMessage =
|
|
609
|
-
error instanceof Error ? error.message : String(error);
|
|
610
|
-
const lowerMessage = errorMessage.toLowerCase();
|
|
611
|
-
|
|
612
|
-
// FC409: Duplicate/Already exists (Non-retryable)
|
|
613
|
-
if (
|
|
614
|
-
lowerMessage.includes('already exists') ||
|
|
615
|
-
lowerMessage.includes('duplicate') ||
|
|
616
|
-
lowerMessage.includes('unique constraint') ||
|
|
617
|
-
lowerMessage.includes('entity already exists')
|
|
618
|
-
) {
|
|
619
|
-
return {
|
|
620
|
-
statusCode: 'FC409',
|
|
621
|
-
retryable: false,
|
|
622
|
-
message: 'Entity already exists. Duplicate request detected.',
|
|
623
|
-
};
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
// FC504: Timeout (Retryable)
|
|
627
|
-
if (
|
|
628
|
-
lowerMessage.includes('timeout') ||
|
|
629
|
-
lowerMessage.includes('timed out') ||
|
|
630
|
-
lowerMessage.includes('request timeout') ||
|
|
631
|
-
(error instanceof Error && error.name === 'TimeoutError')
|
|
632
|
-
) {
|
|
633
|
-
return {
|
|
634
|
-
statusCode: 'FC504',
|
|
635
|
-
retryable: true,
|
|
636
|
-
message: 'Request timeout. Please retry.',
|
|
637
|
-
};
|
|
638
|
-
}
|
|
639
|
-
|
|
640
|
-
// FC400: Bad Request (Non-retryable)
|
|
641
|
-
// Validation errors, missing fields, invalid format
|
|
642
|
-
if (
|
|
643
|
-
lowerMessage.includes('required') ||
|
|
644
|
-
lowerMessage.includes('missing') ||
|
|
645
|
-
lowerMessage.includes('invalid') ||
|
|
646
|
-
lowerMessage.includes('bad request') ||
|
|
647
|
-
lowerMessage.includes('validation') ||
|
|
648
|
-
lowerMessage.includes('not found') ||
|
|
649
|
-
lowerMessage.includes('does not exist')
|
|
650
|
-
) {
|
|
651
|
-
return {
|
|
652
|
-
statusCode: 'FC400',
|
|
653
|
-
retryable: false,
|
|
654
|
-
message: 'Bad request. Please verify payload and try again.',
|
|
655
|
-
};
|
|
656
|
-
}
|
|
657
|
-
|
|
658
|
-
// Default: FC400 for unknown errors (Non-retryable)
|
|
659
|
-
return {
|
|
660
|
-
statusCode: 'FC400',
|
|
661
|
-
retryable: false,
|
|
662
|
-
message: errorMessage,
|
|
663
|
-
};
|
|
664
|
-
}
|
|
665
|
-
|
|
666
|
-
/**
|
|
667
|
-
* Create success response with FC200 status
|
|
668
|
-
*/
|
|
669
|
-
export function createSuccessResponse<T extends Record<string, unknown>>(
|
|
670
|
-
data: T
|
|
671
|
-
): T & { statusCode: 'FC200'; retryable: false } {
|
|
672
|
-
return {
|
|
673
|
-
...data,
|
|
674
|
-
statusCode: 'FC200' as const,
|
|
675
|
-
retryable: false,
|
|
676
|
-
};
|
|
677
|
-
}
|
|
678
|
-
```
|
|
679
|
-
|
|
680
|
-
---
|
|
681
|
-
|
|
682
|
-
## 5. Service: Order Update Processor (`src/services/order-update.service.ts`)
|
|
683
|
-
|
|
684
|
-
```typescript
|
|
685
|
-
/**
|
|
686
|
-
* Order Update Service
|
|
687
|
-
*
|
|
688
|
-
* Processes order update requests: extracts order reference and attributes from XML payload,
|
|
689
|
-
* queries Fluent Commerce to get order ID, and updates order attributes via GraphQL mutation.
|
|
690
|
-
* Handles Versori XML parsing format ($ for attributes, _ for text).
|
|
691
|
-
*/
|
|
692
|
-
|
|
693
|
-
import {
|
|
694
|
-
createClient,
|
|
695
|
-
XMLParserService,
|
|
696
|
-
} from '@fluentcommerce/fc-connect-sdk';
|
|
697
|
-
import { Buffer } from 'node:buffer';
|
|
698
|
-
import { mapErrorToFCStatus, createSuccessResponse } from '../utils/response-status.utils';
|
|
699
|
-
import type { FluentClient } from '@fluentcommerce/fc-connect-sdk';
|
|
700
|
-
import type {
|
|
701
|
-
OrderUpdatePayload,
|
|
702
|
-
OrderUpdateResult,
|
|
703
|
-
VersoriContext,
|
|
704
|
-
AttributeInput,
|
|
705
|
-
OrderQueryResult,
|
|
706
|
-
VersoriXMLParsed,
|
|
707
|
-
} from '../types/order-update.types';
|
|
708
|
-
|
|
709
|
-
/**
|
|
710
|
-
* GraphQL query to get order by reference
|
|
711
|
-
*/
|
|
712
|
-
const GET_ORDER_QUERY = `
|
|
713
|
-
query GetOrder($ref: String!) {
|
|
714
|
-
order(ref: $ref) {
|
|
715
|
-
id
|
|
716
|
-
ref
|
|
717
|
-
status
|
|
718
|
-
attributes {
|
|
719
|
-
name
|
|
720
|
-
type
|
|
721
|
-
value
|
|
722
|
-
}
|
|
723
|
-
}
|
|
724
|
-
}
|
|
725
|
-
`;
|
|
726
|
-
|
|
727
|
-
/**
|
|
728
|
-
* GraphQL mutation to update order
|
|
729
|
-
*/
|
|
730
|
-
const UPDATE_ORDER_MUTATION = `
|
|
731
|
-
mutation UpdateOrder($input: UpdateOrderInput!) {
|
|
732
|
-
updateOrder(input: $input) {
|
|
733
|
-
id
|
|
734
|
-
ref
|
|
735
|
-
status
|
|
736
|
-
attributes {
|
|
737
|
-
name
|
|
738
|
-
type
|
|
739
|
-
value
|
|
740
|
-
}
|
|
741
|
-
}
|
|
742
|
-
}
|
|
743
|
-
`;
|
|
744
|
-
|
|
745
|
-
/**
|
|
746
|
-
* Extract text content from Versori XML parsed format
|
|
747
|
-
* Handles both object format ({ _: "text" }) and direct string
|
|
748
|
-
*/
|
|
749
|
-
function extractTextContent(value: VersoriXMLParsed | string | undefined): string | null {
|
|
750
|
-
if (!value) {
|
|
751
|
-
return null;
|
|
752
|
-
}
|
|
753
|
-
|
|
754
|
-
if (typeof value === 'string') {
|
|
755
|
-
return value;
|
|
756
|
-
}
|
|
757
|
-
|
|
758
|
-
if (typeof value === 'object' && '_' in value) {
|
|
759
|
-
const text = value._;
|
|
760
|
-
return typeof text === 'string' ? text : null;
|
|
761
|
-
}
|
|
762
|
-
|
|
763
|
-
return null;
|
|
764
|
-
}
|
|
765
|
-
|
|
766
|
-
/**
|
|
767
|
-
* Extract order reference from XML payload
|
|
768
|
-
*
|
|
769
|
-
* Supports multiple XML formats:
|
|
770
|
-
* - <orderUpdate><orderRef>ORD-12345</orderRef></orderUpdate>
|
|
771
|
-
* - <orderUpdate orderRef="ORD-12345"/>
|
|
772
|
-
* - <order ref="ORD-12345"/>
|
|
773
|
-
*/
|
|
774
|
-
function extractOrderRef(payload: OrderUpdatePayload): string | null {
|
|
775
|
-
// Try orderUpdate.orderRef (text content)
|
|
776
|
-
if (payload.orderUpdate) {
|
|
777
|
-
const orderUpdate = payload.orderUpdate;
|
|
778
|
-
|
|
779
|
-
// Try orderUpdate.orderRef as text
|
|
780
|
-
if (orderUpdate.orderRef) {
|
|
781
|
-
const ref = extractTextContent(orderUpdate.orderRef);
|
|
782
|
-
if (ref) {
|
|
783
|
-
return ref;
|
|
784
|
-
}
|
|
785
|
-
if (typeof orderUpdate.orderRef === 'string') {
|
|
786
|
-
return orderUpdate.orderRef;
|
|
787
|
-
}
|
|
788
|
-
}
|
|
789
|
-
|
|
790
|
-
// Try orderUpdate.$?.orderRef (root attribute)
|
|
791
|
-
if ('$' in orderUpdate && orderUpdate.$) {
|
|
792
|
-
const attrs = orderUpdate.$ as Record<string, unknown>;
|
|
793
|
-
if (attrs && typeof attrs.orderRef === 'string') {
|
|
794
|
-
return attrs.orderRef;
|
|
795
|
-
}
|
|
796
|
-
}
|
|
797
|
-
}
|
|
798
|
-
|
|
799
|
-
// Try direct order element
|
|
800
|
-
if (payload.order) {
|
|
801
|
-
const order = payload.order;
|
|
802
|
-
|
|
803
|
-
// Try order.$?.ref (attribute)
|
|
804
|
-
if (typeof order === 'object' && '$' in order && order.$) {
|
|
805
|
-
const attrs = order.$ as Record<string, unknown>;
|
|
806
|
-
if (attrs && typeof attrs.ref === 'string') {
|
|
807
|
-
return attrs.ref;
|
|
808
|
-
}
|
|
809
|
-
}
|
|
810
|
-
|
|
811
|
-
// Try order.ref (text content)
|
|
812
|
-
if (typeof order === 'object' && 'ref' in order) {
|
|
813
|
-
const ref = extractTextContent(order.ref as VersoriXMLParsed);
|
|
814
|
-
if (ref) {
|
|
815
|
-
return ref;
|
|
816
|
-
}
|
|
817
|
-
}
|
|
818
|
-
}
|
|
819
|
-
|
|
820
|
-
return null;
|
|
821
|
-
}
|
|
822
|
-
|
|
823
|
-
/**
|
|
824
|
-
* Extract attributes from XML payload
|
|
825
|
-
*
|
|
826
|
-
* PRIMARY FORMAT: Flat structure - all direct children of orderUpdate (except orderRef and metadata)
|
|
827
|
-
* - <orderUpdate><orderRef>ORD-123</orderRef><externalOrderId>EXT-123</externalOrderId></orderUpdate>
|
|
828
|
-
*
|
|
829
|
-
* Supports multiple XML formats:
|
|
830
|
-
* - PRIMARY: Flat structure: <externalOrderId>...</externalOrderId>
|
|
831
|
-
* - ALTERNATIVE: <attributes><attribute><name>...</name><value>...</value></attribute></attributes>
|
|
832
|
-
* - ALTERNATIVE: <attributes><attribute name="..." value="..."/></attributes>
|
|
833
|
-
*/
|
|
834
|
-
function extractAttributes(payload: OrderUpdatePayload): AttributeInput[] {
|
|
835
|
-
const attributes: AttributeInput[] = [];
|
|
836
|
-
const metadataFields = ['orderRef', 'attributes', '$', '_', 'updateReason', 'updatedBy', 'timestamp'];
|
|
837
|
-
|
|
838
|
-
// ✅ PRIMARY FORMAT: Flat structure - all direct children of orderUpdate (except orderRef and metadata)
|
|
839
|
-
// Example: <orderUpdate><orderRef>ORD-123</orderRef><externalOrderId>EXT-123</externalOrderId><customerNotes>Leave at door</customerNotes></orderUpdate>
|
|
840
|
-
if (payload.orderUpdate && typeof payload.orderUpdate === 'object') {
|
|
841
|
-
for (const [key, value] of Object.entries(payload.orderUpdate)) {
|
|
842
|
-
// Skip metadata fields
|
|
843
|
-
if (metadataFields.includes(key)) {
|
|
844
|
-
continue;
|
|
845
|
-
}
|
|
846
|
-
|
|
847
|
-
const textValue = extractTextContent(value as VersoriXMLParsed);
|
|
848
|
-
if (textValue !== null && textValue !== undefined) {
|
|
849
|
-
// Auto-detect type from value (basic detection for XML - mostly STRING)
|
|
850
|
-
// For more complex types, use the attributes format
|
|
851
|
-
let type: AttributeInput['type'] = 'STRING';
|
|
852
|
-
|
|
853
|
-
// Try to detect number
|
|
854
|
-
const numValue = Number(textValue);
|
|
855
|
-
if (!isNaN(numValue) && textValue.trim() !== '') {
|
|
856
|
-
type = Number.isInteger(numValue) ? 'INTEGER' : 'FLOAT';
|
|
857
|
-
}
|
|
858
|
-
// Try to detect boolean
|
|
859
|
-
else if (textValue.toLowerCase() === 'true' || textValue.toLowerCase() === 'false') {
|
|
860
|
-
type = 'BOOLEAN';
|
|
861
|
-
}
|
|
862
|
-
|
|
863
|
-
attributes.push({
|
|
864
|
-
name: key,
|
|
865
|
-
type,
|
|
866
|
-
value: textValue,
|
|
867
|
-
});
|
|
868
|
-
}
|
|
869
|
-
}
|
|
870
|
-
}
|
|
871
|
-
|
|
872
|
-
// ALTERNATIVE FORMAT: orderUpdate.attributes.attribute (nested structure)
|
|
873
|
-
// Only used if flat structure didn't yield any attributes
|
|
874
|
-
if (attributes.length === 0 && payload.orderUpdate?.attributes?.attribute) {
|
|
875
|
-
const attrArray = Array.isArray(payload.orderUpdate.attributes.attribute)
|
|
876
|
-
? payload.orderUpdate.attributes.attribute
|
|
877
|
-
: [payload.orderUpdate.attributes.attribute];
|
|
878
|
-
|
|
879
|
-
for (const attr of attrArray) {
|
|
880
|
-
if (typeof attr === 'object') {
|
|
881
|
-
// Element format: <attribute><name>...</name><value>...</value></attribute>
|
|
882
|
-
const name = extractTextContent(attr.name as VersoriXMLParsed) ||
|
|
883
|
-
(attr.$ as Record<string, unknown>)?.name as string;
|
|
884
|
-
const value = extractTextContent(attr.value as VersoriXMLParsed) ||
|
|
885
|
-
(attr.$ as Record<string, unknown>)?.value as string;
|
|
886
|
-
const type = (extractTextContent(attr.type as VersoriXMLParsed) ||
|
|
887
|
-
(attr.$ as Record<string, unknown>)?.type as string || 'STRING') as AttributeInput['type'];
|
|
888
|
-
|
|
889
|
-
if (name && value !== null && value !== undefined) {
|
|
890
|
-
attributes.push({
|
|
891
|
-
name: String(name),
|
|
892
|
-
type: type || 'STRING',
|
|
893
|
-
value: String(value),
|
|
894
|
-
});
|
|
895
|
-
}
|
|
896
|
-
}
|
|
897
|
-
}
|
|
898
|
-
}
|
|
899
|
-
|
|
900
|
-
return attributes;
|
|
901
|
-
}
|
|
902
|
-
|
|
903
|
-
/**
|
|
904
|
-
* Parse webhook payload from Versori context
|
|
905
|
-
*
|
|
906
|
-
* CRITICAL: Versori automatically parses XML when Content-Type: application/xml is set.
|
|
907
|
-
* The ctx.data will be a pre-parsed JavaScript object - no manual parsing needed!
|
|
908
|
-
*
|
|
909
|
-
* Handles both pre-parsed XML (ctx.data) and raw request body (ctx.request) as fallback
|
|
910
|
-
*/
|
|
911
|
-
async function parseWebhookPayload(
|
|
912
|
-
ctx: VersoriContext
|
|
913
|
-
): Promise<OrderUpdatePayload | null> {
|
|
914
|
-
const { log, data, request } = ctx;
|
|
915
|
-
|
|
916
|
-
// ✅ Scenario 1: Pre-parsed XML (Versori auto-parsed when Content-Type: application/xml)
|
|
917
|
-
// This is the PRIMARY path - Versori handles XML parsing automatically
|
|
918
|
-
if (data && typeof data === 'object' && data !== null) {
|
|
919
|
-
log.info('📥 [PARSE] Using Versori auto-parsed XML payload', {
|
|
920
|
-
hasData: true,
|
|
921
|
-
dataKeys: Object.keys(data),
|
|
922
|
-
contentType: request?.headers?.get('content-type'),
|
|
923
|
-
});
|
|
924
|
-
return data as OrderUpdatePayload;
|
|
925
|
-
}
|
|
926
|
-
|
|
927
|
-
// ⚠️ Scenario 2: Raw request body (fallback - should rarely be needed)
|
|
928
|
-
// Only used if Versori didn't auto-parse (e.g., wrong Content-Type header)
|
|
929
|
-
if (request && typeof request.text === 'function') {
|
|
930
|
-
log.warn('📥 [PARSE] Falling back to manual XML parsing (Content-Type may be incorrect)');
|
|
931
|
-
try {
|
|
932
|
-
const rawBody = await request.text();
|
|
933
|
-
if (!rawBody || rawBody.trim() === '') {
|
|
934
|
-
log.error('❌ [PARSE] Request body is empty');
|
|
935
|
-
return null;
|
|
936
|
-
}
|
|
937
|
-
|
|
938
|
-
const parser = new XMLParserService(log);
|
|
939
|
-
const parsed = await parser.parse(rawBody);
|
|
940
|
-
|
|
941
|
-
log.info('📥 [PARSE] Successfully parsed XML payload', {
|
|
942
|
-
parsedKeys: Object.keys(parsed),
|
|
943
|
-
});
|
|
944
|
-
|
|
945
|
-
return parsed as OrderUpdatePayload;
|
|
946
|
-
} catch (parseError: unknown) {
|
|
947
|
-
const errorMessage =
|
|
948
|
-
parseError instanceof Error ? parseError.message : String(parseError);
|
|
949
|
-
log.error('❌ [PARSE] Failed to parse XML payload', {
|
|
950
|
-
error: errorMessage,
|
|
951
|
-
});
|
|
952
|
-
return null;
|
|
953
|
-
}
|
|
954
|
-
}
|
|
955
|
-
|
|
956
|
-
log.error('❌ [PARSE] No valid payload found in context');
|
|
957
|
-
return null;
|
|
958
|
-
}
|
|
959
|
-
|
|
960
|
-
/**
|
|
961
|
-
* Query order by reference to get ID and current state
|
|
962
|
-
*/
|
|
963
|
-
async function queryOrderByRef(
|
|
964
|
-
client: FluentClient,
|
|
965
|
-
orderRef: string,
|
|
966
|
-
log: VersoriContext['log']
|
|
967
|
-
): Promise<OrderQueryResult> {
|
|
968
|
-
log.info('🔍 [QUERY] Querying order by reference', { orderRef });
|
|
969
|
-
|
|
970
|
-
const result = await client.graphql({
|
|
971
|
-
query: GET_ORDER_QUERY,
|
|
972
|
-
variables: { ref: orderRef },
|
|
973
|
-
});
|
|
974
|
-
|
|
975
|
-
if (result.errors && result.errors.length > 0) {
|
|
976
|
-
log.error('❌ [QUERY] GraphQL query returned errors', {
|
|
977
|
-
errors: result.errors,
|
|
978
|
-
orderRef,
|
|
979
|
-
});
|
|
980
|
-
throw new Error(`GraphQL query failed: ${result.errors[0].message}`);
|
|
981
|
-
}
|
|
982
|
-
|
|
983
|
-
const order = (result.data as any)?.order;
|
|
984
|
-
|
|
985
|
-
if (!order) {
|
|
986
|
-
log.error('❌ [QUERY] Order not found', { orderRef });
|
|
987
|
-
throw new Error(`Order not found: ${orderRef}`);
|
|
988
|
-
}
|
|
989
|
-
|
|
990
|
-
log.info('✅ [QUERY] Order found', {
|
|
991
|
-
orderId: order.id,
|
|
992
|
-
orderRef: order.ref,
|
|
993
|
-
status: order.status,
|
|
994
|
-
currentAttributeCount: order.attributes?.length || 0,
|
|
995
|
-
});
|
|
996
|
-
|
|
997
|
-
return order as OrderQueryResult;
|
|
998
|
-
}
|
|
999
|
-
|
|
1000
|
-
/**
|
|
1001
|
-
* Execute order update workflow
|
|
1002
|
-
*
|
|
1003
|
-
* Main orchestration function:
|
|
1004
|
-
* 1. Parse webhook payload
|
|
1005
|
-
* 2. Extract order reference and attributes
|
|
1006
|
-
* 3. Create Fluent client
|
|
1007
|
-
* 4. Query order by reference to get ID
|
|
1008
|
-
* 5. Build update mutation variables
|
|
1009
|
-
* 6. Execute updateOrder mutation
|
|
1010
|
-
*
|
|
1011
|
-
* Note: Authentication handled via Versori connection (fluent_commerce).
|
|
1012
|
-
* Retailer ID is not required for GraphQL mutations - connection handles authentication.
|
|
1013
|
-
*
|
|
1014
|
-
* CRITICAL: UpdateOrderInput requires id: ID! (not ref).
|
|
1015
|
-
* The query step retrieves order.id which must be used in the mutation.
|
|
1016
|
-
*/
|
|
1017
|
-
export async function executeOrderUpdate(
|
|
1018
|
-
ctx: VersoriContext
|
|
1019
|
-
): Promise<OrderUpdateResult> {
|
|
1020
|
-
const { log, activation } = ctx;
|
|
1021
|
-
|
|
1022
|
-
log.info('🚀 [SERVICE] Starting order update processing');
|
|
1023
|
-
|
|
1024
|
-
// STEP 1: Parse webhook payload
|
|
1025
|
-
// ✅ Versori automatically parses XML when Content-Type: application/xml is set
|
|
1026
|
-
// ctx.data will be a pre-parsed JavaScript object - no manual parsing needed!
|
|
1027
|
-
const payload = await parseWebhookPayload(ctx);
|
|
1028
|
-
if (!payload) {
|
|
1029
|
-
log.error('❌ [SERVICE] Failed to parse webhook payload', {
|
|
1030
|
-
hasData: !!ctx.data,
|
|
1031
|
-
hasRequest: !!ctx.request,
|
|
1032
|
-
});
|
|
1033
|
-
throw new Error(
|
|
1034
|
-
'Failed to parse webhook payload. Ensure Content-Type: application/xml is set in request headers.'
|
|
1035
|
-
);
|
|
1036
|
-
}
|
|
1037
|
-
|
|
1038
|
-
log.info('📥 [SERVICE] Payload parsed successfully (Versori auto-parsed XML)', {
|
|
1039
|
-
payloadKeys: Object.keys(payload),
|
|
1040
|
-
note: 'Versori automatically parses XML when Content-Type: application/xml is set',
|
|
1041
|
-
});
|
|
1042
|
-
|
|
1043
|
-
// STEP 2: Extract order reference
|
|
1044
|
-
const orderRef = extractOrderRef(payload);
|
|
1045
|
-
if (!orderRef) {
|
|
1046
|
-
log.error('❌ [SERVICE] Order reference not found in XML payload', {
|
|
1047
|
-
payloadKeys: Object.keys(payload),
|
|
1048
|
-
expectedPatterns: [
|
|
1049
|
-
'orderUpdate.orderRef',
|
|
1050
|
-
'orderUpdate[@orderRef]',
|
|
1051
|
-
'order[@ref]',
|
|
1052
|
-
],
|
|
1053
|
-
});
|
|
1054
|
-
throw new Error(
|
|
1055
|
-
'Order reference not found in XML payload. Expected patterns: orderUpdate.orderRef, orderUpdate[@orderRef], or order[@ref]'
|
|
1056
|
-
);
|
|
1057
|
-
}
|
|
1058
|
-
|
|
1059
|
-
log.info('✅ [SERVICE] Order reference extracted', { orderRef });
|
|
1060
|
-
|
|
1061
|
-
// STEP 3: Extract attributes
|
|
1062
|
-
const attributes = extractAttributes(payload);
|
|
1063
|
-
if (attributes.length === 0) {
|
|
1064
|
-
log.warn('⚠️ [SERVICE] No attributes provided in payload', { orderRef });
|
|
1065
|
-
throw new Error('At least one attribute is required for order update');
|
|
1066
|
-
}
|
|
1067
|
-
|
|
1068
|
-
log.info('✅ [SERVICE] Attributes extracted', {
|
|
1069
|
-
orderRef,
|
|
1070
|
-
attributeCount: attributes.length,
|
|
1071
|
-
attributeNames: attributes.map(a => a.name),
|
|
1072
|
-
});
|
|
1073
|
-
|
|
1074
|
-
// STEP 4: Create Fluent client
|
|
1075
|
-
// ✅ Pass ctx directly - createClient auto-detects Versori context
|
|
1076
|
-
// Authentication handled via Versori connection (fluent_commerce)
|
|
1077
|
-
const client = await createClient(ctx);
|
|
1078
|
-
|
|
1079
|
-
log.info('✅ [SERVICE] Fluent client created');
|
|
1080
|
-
|
|
1081
|
-
// STEP 5: Query order by reference to get ID
|
|
1082
|
-
// ✅ Query first to:
|
|
1083
|
-
// - Validate order exists (fail-fast if order not found)
|
|
1084
|
-
// - Get order ID for logging and tracking
|
|
1085
|
-
// - Get current order state (optional: can merge with existing attributes)
|
|
1086
|
-
const order = await queryOrderByRef(client, orderRef, log);
|
|
1087
|
-
|
|
1088
|
-
// STEP 6: Build update mutation variables
|
|
1089
|
-
// ✅ CRITICAL: UpdateOrderInput requires id: ID! (not ref)
|
|
1090
|
-
// Use the ID from queryOrderByRef result - this is why we query first!
|
|
1091
|
-
const updateVariables = {
|
|
1092
|
-
input: {
|
|
1093
|
-
id: order.id, // ✅ REQUIRED: UpdateOrderInput.id is ID! (required field)
|
|
1094
|
-
attributes: attributes,
|
|
1095
|
-
},
|
|
1096
|
-
};
|
|
1097
|
-
|
|
1098
|
-
log.info('📤 [SERVICE] Executing updateOrder mutation', {
|
|
1099
|
-
orderRef,
|
|
1100
|
-
orderId: order.id,
|
|
1101
|
-
attributeCount: attributes.length,
|
|
1102
|
-
});
|
|
1103
|
-
|
|
1104
|
-
// STEP 7: Execute updateOrder mutation
|
|
1105
|
-
const mutationResult = await client.graphql({
|
|
1106
|
-
query: UPDATE_ORDER_MUTATION,
|
|
1107
|
-
variables: updateVariables,
|
|
1108
|
-
});
|
|
1109
|
-
|
|
1110
|
-
if (mutationResult.errors && mutationResult.errors.length > 0) {
|
|
1111
|
-
log.error('❌ [SERVICE] GraphQL mutation returned errors', {
|
|
1112
|
-
errors: mutationResult.errors,
|
|
1113
|
-
orderRef,
|
|
1114
|
-
orderId: order.id,
|
|
1115
|
-
});
|
|
1116
|
-
throw new Error(`GraphQL mutation failed: ${mutationResult.errors[0].message}`);
|
|
1117
|
-
}
|
|
1118
|
-
|
|
1119
|
-
const updatedOrder = (mutationResult.data as any)?.updateOrder;
|
|
1120
|
-
|
|
1121
|
-
if (!updatedOrder) {
|
|
1122
|
-
log.error('❌ [SERVICE] No order data returned from mutation', { orderRef });
|
|
1123
|
-
throw new Error('No order data returned from updateOrder mutation');
|
|
1124
|
-
}
|
|
1125
|
-
|
|
1126
|
-
log.info('✅ [SERVICE] Order updated successfully', {
|
|
1127
|
-
orderRef,
|
|
1128
|
-
orderId: updatedOrder.id,
|
|
1129
|
-
attributesUpdated: attributes.length,
|
|
1130
|
-
});
|
|
1131
|
-
|
|
1132
|
-
// Return success response with FC200 status code
|
|
1133
|
-
return createSuccessResponse({
|
|
1134
|
-
success: true,
|
|
1135
|
-
orderRef,
|
|
1136
|
-
orderId: updatedOrder.id,
|
|
1137
|
-
attributesUpdated: attributes.length,
|
|
1138
|
-
message: 'Order attributes updated successfully',
|
|
1139
|
-
});
|
|
1140
|
-
}
|
|
1141
|
-
```
|
|
1142
|
-
|
|
1143
|
-
---
|
|
1144
|
-
|
|
1145
|
-
## 5. Configuration
|
|
1146
|
-
|
|
1147
|
-
### Activation Variables
|
|
1148
|
-
|
|
1149
|
-
No activation variables required. Authentication is handled via Versori connection (`fluent_commerce`).
|
|
1150
|
-
|
|
1151
|
-
**Note:** Configure Fluent Commerce credentials in the Versori connection settings. The connection handles authentication automatically.
|
|
1152
|
-
|
|
1153
|
-
### Webhook Connection
|
|
1154
|
-
|
|
1155
|
-
Create a Versori connection for webhook authentication:
|
|
1156
|
-
|
|
1157
|
-
1. **Connection Name:** `order-update-webhook`
|
|
1158
|
-
2. **Type:** API Key or Basic Auth
|
|
1159
|
-
3. **Purpose:** Authenticate incoming webhook requests
|
|
1160
|
-
|
|
1161
|
-
**Example webhook call:**
|
|
1162
|
-
|
|
1163
|
-
```bash
|
|
1164
|
-
curl -X POST https://{workspace}.versori.run/order-update \
|
|
1165
|
-
-H "Content-Type: application/xml" \
|
|
1166
|
-
-H "X-API-Key: your-api-key" \
|
|
1167
|
-
--data-binary @order-update.xml
|
|
1168
|
-
```
|
|
1169
|
-
|
|
1170
|
-
**Example XML file (`order-update.xml`):**
|
|
1171
|
-
|
|
1172
|
-
```xml
|
|
1173
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
|
1174
|
-
<orderUpdate>
|
|
1175
|
-
<orderRef>ORD-12345</orderRef>
|
|
1176
|
-
<externalOrderId>EXT-123456789</externalOrderId>
|
|
1177
|
-
<customerNotes>Please leave package at front door</customerNotes>
|
|
1178
|
-
<priority>high</priority>
|
|
1179
|
-
</orderUpdate>
|
|
1180
|
-
```
|
|
1181
|
-
|
|
1182
|
-
**CRITICAL: Content-Type Header**
|
|
1183
|
-
|
|
1184
|
-
The `Content-Type: application/xml` header is **required** for Versori to automatically parse the XML. Without this header, Versori will not parse the XML and you'll need to manually parse using `XMLParserService`.
|
|
1185
|
-
|
|
1186
|
-
**✅ CORRECT:**
|
|
1187
|
-
```bash
|
|
1188
|
-
curl -X POST https://{workspace}.versori.run/order-update \
|
|
1189
|
-
-H "Content-Type: application/xml" \ # ✅ Required!
|
|
1190
|
-
-H "X-API-Key: your-api-key" \
|
|
1191
|
-
--data-binary @order-update.xml
|
|
1192
|
-
```
|
|
1193
|
-
|
|
1194
|
-
**❌ WRONG:**
|
|
1195
|
-
```bash
|
|
1196
|
-
curl -X POST https://{workspace}.versori.run/order-update \
|
|
1197
|
-
-H "Content-Type: text/plain" \ # ❌ Wrong Content-Type
|
|
1198
|
-
-H "X-API-Key: your-api-key" \
|
|
1199
|
-
--data-binary @order-update.xml
|
|
1200
|
-
```
|
|
1201
|
-
|
|
1202
|
-
---
|
|
1203
|
-
|
|
1204
|
-
## 6. Error Handling
|
|
1205
|
-
|
|
1206
|
-
The template includes comprehensive error handling:
|
|
1207
|
-
|
|
1208
|
-
### Payload Parsing Errors
|
|
1209
|
-
|
|
1210
|
-
- **Empty payload:** Returns error response
|
|
1211
|
-
- **Invalid XML:** Returns error with parse details
|
|
1212
|
-
- **Missing order reference:** Returns error with expected patterns
|
|
1213
|
-
- **Missing attributes:** Returns error if no attributes provided
|
|
1214
|
-
|
|
1215
|
-
### Order Query Errors
|
|
1216
|
-
|
|
1217
|
-
- **Order not found:** Returns error with order reference
|
|
1218
|
-
- **GraphQL query errors:** Returns error with GraphQL error details
|
|
1219
|
-
|
|
1220
|
-
### Mutation Errors
|
|
1221
|
-
|
|
1222
|
-
- **GraphQL mutation errors:** Returns error with mutation error details
|
|
1223
|
-
- **No response data:** Returns error if mutation doesn't return order data
|
|
1224
|
-
|
|
1225
|
-
### Response Format with FC Status Codes
|
|
1226
|
-
|
|
1227
|
-
**Success Response (FC200):**
|
|
1228
|
-
```json
|
|
1229
|
-
{
|
|
1230
|
-
"success": true,
|
|
1231
|
-
"statusCode": "FC200",
|
|
1232
|
-
"orderRef": "ORD-12345",
|
|
1233
|
-
"orderId": "12345",
|
|
1234
|
-
"attributesUpdated": 3,
|
|
1235
|
-
"message": "Order attributes updated successfully"
|
|
1236
|
-
}
|
|
1237
|
-
```
|
|
1238
|
-
|
|
1239
|
-
**Error Responses:**
|
|
1240
|
-
|
|
1241
|
-
**FC400 - Bad Request (Non-retryable):**
|
|
1242
|
-
```json
|
|
1243
|
-
{
|
|
1244
|
-
"success": false,
|
|
1245
|
-
"statusCode": "FC400",
|
|
1246
|
-
"retryable": false,
|
|
1247
|
-
"error": "Bad request. Please verify payload and try again.",
|
|
1248
|
-
"details": "Order reference (orderRef) is required in payload"
|
|
1249
|
-
}
|
|
1250
|
-
```
|
|
1251
|
-
|
|
1252
|
-
**FC409 - Duplicate/Already Exists (Non-retryable):**
|
|
1253
|
-
```json
|
|
1254
|
-
{
|
|
1255
|
-
"success": false,
|
|
1256
|
-
"statusCode": "FC409",
|
|
1257
|
-
"retryable": false,
|
|
1258
|
-
"error": "Entity already exists. Duplicate request detected.",
|
|
1259
|
-
"details": "Order with reference ORD-12345 already exists"
|
|
1260
|
-
}
|
|
1261
|
-
```
|
|
1262
|
-
|
|
1263
|
-
**FC504 - Timeout (Retryable):**
|
|
1264
|
-
```json
|
|
1265
|
-
{
|
|
1266
|
-
"success": false,
|
|
1267
|
-
"statusCode": "FC504",
|
|
1268
|
-
"retryable": true,
|
|
1269
|
-
"error": "Request timeout. Please retry.",
|
|
1270
|
-
"details": "GraphQL mutation timed out after 30 seconds"
|
|
1271
|
-
}
|
|
1272
|
-
```
|
|
1273
|
-
|
|
1274
|
-
---
|
|
1275
|
-
|
|
1276
|
-
## 7. Testing
|
|
1277
|
-
|
|
1278
|
-
### Test with cURL - Flat Structure Format
|
|
1279
|
-
|
|
1280
|
-
**Create `order-update.xml`:**
|
|
1281
|
-
```xml
|
|
1282
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
|
1283
|
-
<orderUpdate>
|
|
1284
|
-
<orderRef>ORD-12345</orderRef>
|
|
1285
|
-
<externalOrderId>EXT-123456789</externalOrderId>
|
|
1286
|
-
<customerNotes>Please leave package at front door</customerNotes>
|
|
1287
|
-
<priority>high</priority>
|
|
1288
|
-
</orderUpdate>
|
|
1289
|
-
```
|
|
1290
|
-
|
|
1291
|
-
**Send with cURL:**
|
|
1292
|
-
```bash
|
|
1293
|
-
curl -X POST https://{workspace}.versori.run/order-update \
|
|
1294
|
-
-H "Content-Type: application/xml" \
|
|
1295
|
-
-H "X-API-Key: your-api-key" \
|
|
1296
|
-
--data-binary @order-update.xml
|
|
1297
|
-
```
|
|
1298
|
-
|
|
1299
|
-
### Test with Inline XML
|
|
1300
|
-
|
|
1301
|
-
```bash
|
|
1302
|
-
# Test with inline XML (flat format)
|
|
1303
|
-
curl -X POST https://{workspace}.versori.run/order-update \
|
|
1304
|
-
-H "Content-Type: application/xml" \
|
|
1305
|
-
-H "X-API-Key: your-api-key" \
|
|
1306
|
-
-d '<?xml version="1.0" encoding="UTF-8"?><orderUpdate><orderRef>ORD-12345</orderRef><externalOrderId>EXT-123456789</externalOrderId><customerNotes>Please leave package at front door</customerNotes></orderUpdate>'
|
|
1307
|
-
```
|
|
1308
|
-
|
|
1309
|
-
### Complete Sample XML Files
|
|
1310
|
-
|
|
1311
|
-
**Example 1: External System Reference (`external-reference-update.xml`)**
|
|
1312
|
-
```xml
|
|
1313
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
|
1314
|
-
<orderUpdate>
|
|
1315
|
-
<orderRef>ORD-0017326966182</orderRef>
|
|
1316
|
-
<externalOrderId>EXT-123456789</externalOrderId>
|
|
1317
|
-
<sourceSystem>ERP-SAP</sourceSystem>
|
|
1318
|
-
<integrationId>INT-987654321</integrationId>
|
|
1319
|
-
</orderUpdate>
|
|
1320
|
-
```
|
|
1321
|
-
|
|
1322
|
-
**Example 2: Customer Notes and Business Flags (`customer-notes-update.xml`)**
|
|
1323
|
-
```xml
|
|
1324
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
|
1325
|
-
<orderUpdate>
|
|
1326
|
-
<orderRef>ORD-0017326966182</orderRef>
|
|
1327
|
-
<customerNotes>Please leave package at front door</customerNotes>
|
|
1328
|
-
<priority>high</priority>
|
|
1329
|
-
<tags>rush,gift</tags>
|
|
1330
|
-
<specialHandling>true</specialHandling>
|
|
1331
|
-
</orderUpdate>
|
|
1332
|
-
```
|
|
1333
|
-
|
|
1334
|
-
### Expected Success Response
|
|
1335
|
-
|
|
1336
|
-
```json
|
|
1337
|
-
{
|
|
1338
|
-
"success": true,
|
|
1339
|
-
"statusCode": "FC200",
|
|
1340
|
-
"orderRef": "ORD-12345",
|
|
1341
|
-
"orderId": "12345",
|
|
1342
|
-
"attributesUpdated": 1,
|
|
1343
|
-
"message": "Order attributes updated successfully"
|
|
1344
|
-
}
|
|
1345
|
-
```
|
|
1346
|
-
|
|
1347
|
-
**FC Status Code:** `FC200` - OK (Successfully - No further action needed)
|
|
1348
|
-
|
|
1349
|
-
### Expected Error Responses
|
|
1350
|
-
|
|
1351
|
-
**FC400 - Bad Request:**
|
|
1352
|
-
```json
|
|
1353
|
-
{
|
|
1354
|
-
"success": false,
|
|
1355
|
-
"statusCode": "FC400",
|
|
1356
|
-
"retryable": false,
|
|
1357
|
-
"error": "Bad request. Please verify payload and try again.",
|
|
1358
|
-
"details": "Order not found: ORD-12345"
|
|
1359
|
-
}
|
|
1360
|
-
```
|
|
1361
|
-
|
|
1362
|
-
**FC409 - Duplicate/Already Exists:**
|
|
1363
|
-
```json
|
|
1364
|
-
{
|
|
1365
|
-
"success": false,
|
|
1366
|
-
"statusCode": "FC409",
|
|
1367
|
-
"retryable": false,
|
|
1368
|
-
"error": "Entity already exists. Duplicate request detected.",
|
|
1369
|
-
"details": "Order with reference ORD-12345 already exists"
|
|
1370
|
-
}
|
|
1371
|
-
```
|
|
1372
|
-
|
|
1373
|
-
**FC504 - Timeout:**
|
|
1374
|
-
```json
|
|
1375
|
-
{
|
|
1376
|
-
"success": false,
|
|
1377
|
-
"statusCode": "FC504",
|
|
1378
|
-
"retryable": true,
|
|
1379
|
-
"error": "Request timeout. Please retry.",
|
|
1380
|
-
"details": "GraphQL mutation timed out"
|
|
1381
|
-
}
|
|
1382
|
-
```
|
|
1383
|
-
|
|
1384
|
-
---
|
|
1385
|
-
|
|
1386
|
-
## 8. Customization
|
|
1387
|
-
|
|
1388
|
-
### Custom Order Reference Element
|
|
1389
|
-
|
|
1390
|
-
If your XML uses different element names, modify `extractOrderRef()`:
|
|
1391
|
-
|
|
1392
|
-
```typescript
|
|
1393
|
-
function extractOrderRef(payload: OrderUpdatePayload): string | null {
|
|
1394
|
-
// Add your custom element extraction logic
|
|
1395
|
-
if (payload.customUpdate && typeof payload.customUpdate === 'object') {
|
|
1396
|
-
const update = payload.customUpdate;
|
|
1397
|
-
if ('$' in update && update.$) {
|
|
1398
|
-
const attrs = update.$ as Record<string, unknown>;
|
|
1399
|
-
if (attrs && typeof attrs.orderId === 'string') {
|
|
1400
|
-
return attrs.orderId;
|
|
1401
|
-
}
|
|
1402
|
-
}
|
|
1403
|
-
}
|
|
1404
|
-
// ... existing logic
|
|
1405
|
-
}
|
|
1406
|
-
```
|
|
1407
|
-
|
|
1408
|
-
### Custom Attribute Extraction
|
|
1409
|
-
|
|
1410
|
-
To customize how attributes are extracted from XML:
|
|
1411
|
-
|
|
1412
|
-
```typescript
|
|
1413
|
-
function extractAttributes(payload: OrderUpdatePayload): AttributeInput[] {
|
|
1414
|
-
// Add custom extraction logic for your XML structure
|
|
1415
|
-
// e.g., handle nested attribute structures, namespaces, etc.
|
|
1416
|
-
}
|
|
1417
|
-
```
|
|
1418
|
-
|
|
1419
|
-
### Merge with Existing Attributes
|
|
1420
|
-
|
|
1421
|
-
To merge new attributes with existing ones (instead of replacing):
|
|
1422
|
-
|
|
1423
|
-
```typescript
|
|
1424
|
-
// In executeOrderUpdate function, after queryOrderByRef:
|
|
1425
|
-
const existingAttributes = order.attributes || [];
|
|
1426
|
-
const mergedAttributes = [
|
|
1427
|
-
...existingAttributes,
|
|
1428
|
-
...attributes, // New attributes override existing ones with same name
|
|
1429
|
-
];
|
|
1430
|
-
|
|
1431
|
-
const updateVariables = {
|
|
1432
|
-
input: {
|
|
1433
|
-
id: order.id, // ✅ REQUIRED: Use ID from query result
|
|
1434
|
-
attributes: mergedAttributes,
|
|
1435
|
-
},
|
|
1436
|
-
};
|
|
1437
|
-
```
|
|
1438
|
-
|
|
1439
|
-
---
|
|
1440
|
-
|
|
1441
|
-
## Summary
|
|
1442
|
-
|
|
1443
|
-
✅ **DO:**
|
|
1444
|
-
- Use Versori connection for Fluent Commerce credentials
|
|
1445
|
-
- Query order first to validate existence and get ID
|
|
1446
|
-
- Use `id` from query result in UpdateOrderInput (required field)
|
|
1447
|
-
- Handle Versori XML format ($ for attributes, _ for text)
|
|
1448
|
-
- Support multiple XML attribute formats
|
|
1449
|
-
- Log all steps for debugging
|
|
1450
|
-
- Return clear error messages
|
|
1451
|
-
|
|
1452
|
-
❌ **DON'T:**
|
|
1453
|
-
- Hardcode credentials in code
|
|
1454
|
-
- Skip order query validation
|
|
1455
|
-
- Use `ref` in UpdateOrderInput (must use `id: ID!`)
|
|
1456
|
-
- Assume payload format without validation
|
|
1457
|
-
- Skip error handling
|
|
1458
|
-
- Ignore Versori XML parsing format ($ and _)
|
|
1459
|
-
|
|
1460
|
-
---
|
|
1461
|
-
|
|
1462
|
-
## Next Steps
|
|
1463
|
-
|
|
1464
|
-
1. **Deploy:** Copy code to your Versori project
|
|
1465
|
-
2. **Configure:** Set up Fluent Commerce connection and activation variables
|
|
1466
|
-
3. **Test:** Test webhook with sample XML payloads
|
|
1467
|
-
4. **Monitor:** Check Versori logs for order updates
|
|
1468
|
-
|
|
1469
|
-
For more templates, see:
|
|
1470
|
-
- [JSON Payload Template](./template-ingestion-payload-json-order-update-graphql.md)
|
|
1471
|
-
- [Other GraphQL Mutation Templates](./graphql-mutations-guide.md)
|
|
1472
|
-
|
|
1
|
+
---
|
|
2
|
+
template_id: tpl-ingest-payload-xml-to-order-update-graphql
|
|
3
|
+
canonical_filename: template-ingestion-payload-xml-order-update-graphql.md
|
|
4
|
+
sdk_version: latest
|
|
5
|
+
runtime: versori
|
|
6
|
+
direction: ingestion
|
|
7
|
+
source: payload-xml
|
|
8
|
+
destination: fluent-graphql
|
|
9
|
+
entity: order
|
|
10
|
+
format: xml
|
|
11
|
+
logging: versori
|
|
12
|
+
status: stable
|
|
13
|
+
features:
|
|
14
|
+
- direct-payload-processing
|
|
15
|
+
- xml-parsing
|
|
16
|
+
- graphql-mutations
|
|
17
|
+
- order-query-before-update
|
|
18
|
+
- attribute-updates
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
# Template: Ingestion - Payload XML to Order Update GraphQL
|
|
22
|
+
|
|
23
|
+
**SDK Version:** @fluentcommerce/fc-connect-sdk@latest
|
|
24
|
+
**Last Updated:** 2025-01-24
|
|
25
|
+
**Deployment Target:** Versori Platform
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## 📋 Implementation Prompt
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
I need a Versori webhook ingestion that:
|
|
33
|
+
|
|
34
|
+
1) Receives XML payload directly in webhook request body with orderRef and attributes
|
|
35
|
+
2) Parses XML to extract order reference and attributes (handles Versori $ format for attributes)
|
|
36
|
+
3) Queries Fluent Commerce to get order ID by order reference
|
|
37
|
+
4) Updates order attributes using updateOrder GraphQL mutation
|
|
38
|
+
5) Returns success/error response
|
|
39
|
+
6) Uses native Versori log from context
|
|
40
|
+
|
|
41
|
+
Use the loaded docs to fill in SDK specifics and best practices.
|
|
42
|
+
Keep the structure identical to the template; only adapt where needed.
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## 📋 Template Overview
|
|
48
|
+
|
|
49
|
+
This connector runs on the Versori platform. It receives XML payloads directly via webhook with order reference and attributes, queries Fluent Commerce to get the order ID, and updates order attributes using GraphQL mutations. Most operational settings (Fluent account/connection) are configured via activation variables.
|
|
50
|
+
|
|
51
|
+
### What This Template Does
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
┌────────────────────────────────────────────────────────────────┐
|
|
55
|
+
│ INGESTION WORKFLOW │
|
|
56
|
+
└────────────────────────────────────────────────────────────────┘
|
|
57
|
+
|
|
58
|
+
1. TRIGGER
|
|
59
|
+
└─ Webhook: HTTP POST endpoint receives XML payload
|
|
60
|
+
|
|
61
|
+
2. RECEIVE PAYLOAD
|
|
62
|
+
├─ Extract XML from request body (ctx.data or ctx.request)
|
|
63
|
+
├─ Validate payload structure
|
|
64
|
+
└─ Log incoming request
|
|
65
|
+
|
|
66
|
+
3. PARSE XML
|
|
67
|
+
├─ Parse XML payload (Versori auto-parses with $ format)
|
|
68
|
+
├─ Extract order reference (handles $ for attributes, _ for text)
|
|
69
|
+
├─ Extract attributes array
|
|
70
|
+
└─ Validate required fields
|
|
71
|
+
|
|
72
|
+
4. QUERY ORDER
|
|
73
|
+
├─ Query Fluent Commerce: order(ref: orderRef)
|
|
74
|
+
├─ Get order ID and current state
|
|
75
|
+
├─ Validate order exists
|
|
76
|
+
└─ Log order details
|
|
77
|
+
|
|
78
|
+
5. BUILD UPDATE MUTATION
|
|
79
|
+
├─ Construct UpdateOrderInput with ref
|
|
80
|
+
├─ Map attributes to AttributeInput format
|
|
81
|
+
├─ Preserve existing attributes (optional merge)
|
|
82
|
+
└─ Build GraphQL mutation variables
|
|
83
|
+
|
|
84
|
+
6. EXECUTE MUTATION
|
|
85
|
+
├─ Execute updateOrder GraphQL mutation
|
|
86
|
+
├─ Handle success/error responses
|
|
87
|
+
└─ Log mutation result
|
|
88
|
+
|
|
89
|
+
7. RETURN RESPONSE
|
|
90
|
+
├─ Return success with order details
|
|
91
|
+
└─ Return error with details if failed
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Key Features
|
|
95
|
+
|
|
96
|
+
- **Direct Payload Processing**: Receives XML payload directly in webhook request body
|
|
97
|
+
- **Versori XML Parsing**: Handles Versori auto-parsed XML format ($ for attributes, _ for text)
|
|
98
|
+
- **Order Query First**: Queries order by ref to get ID and validate existence
|
|
99
|
+
- **GraphQL Mutation**: Uses updateOrder mutation to update attributes
|
|
100
|
+
- **Attribute Mapping**: Converts XML attributes to Fluent AttributeInput format
|
|
101
|
+
- **Error Handling**: Comprehensive error handling with detailed logging
|
|
102
|
+
- **Native Versori Logging**: Uses Versori log from context
|
|
103
|
+
|
|
104
|
+
### 📦 Package Information
|
|
105
|
+
|
|
106
|
+
**SDK:** [@fluentcommerce/fc-connect-sdk](https://www.npmjs.com/package/@fluentcommerce/fc-connect-sdk) `latest`
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
npm install @fluentcommerce/fc-connect-sdk@latest
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
**Templates are designed for direct deployment; customize via activation variables.**
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## 📦 SDK Imports (Verified - Versori Optimized)
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
// ✅ VERIFIED IMPORTS - These match actual SDK exports
|
|
122
|
+
import { Buffer } from 'node:buffer'; // Required for Versori/Deno runtime
|
|
123
|
+
import {
|
|
124
|
+
createClient, // Universal client factory
|
|
125
|
+
XMLParserService, // XML parsing utility
|
|
126
|
+
} from '@fluentcommerce/fc-connect-sdk';
|
|
127
|
+
|
|
128
|
+
import type { FluentClient } from '@fluentcommerce/fc-connect-sdk';
|
|
129
|
+
|
|
130
|
+
// Versori platform imports
|
|
131
|
+
import { webhook, http } from '@versori/run';
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
**Note:** All imports are from actual SDK exports - this code compiles and runs as-is.
|
|
135
|
+
|
|
136
|
+
**⚠️ CRITICAL - Buffer Import Required:**
|
|
137
|
+
|
|
138
|
+
The `Buffer` import from `node:buffer` is **required** for Versori/Deno runtime compatibility:
|
|
139
|
+
- Versori and Deno do NOT have `Buffer` as a global (unlike Node.js)
|
|
140
|
+
- Without this import, code will crash with `ReferenceError: Buffer is not defined`
|
|
141
|
+
- Always include this import in Versori templates, even if not directly used in the template code
|
|
142
|
+
- Required for SDK internal operations and any Buffer usage in your code
|
|
143
|
+
|
|
144
|
+
**✅ VERSORI PLATFORM - Use Native Logs:**
|
|
145
|
+
|
|
146
|
+
- Use `log` from context: `const { log } = ctx;`
|
|
147
|
+
- Native Versori logs are simpler and automatically integrated with platform monitoring
|
|
148
|
+
|
|
149
|
+
**✅ VERSORI XML PARSING:**
|
|
150
|
+
|
|
151
|
+
Versori automatically parses XML payloads when `Content-Type: application/xml` is set:
|
|
152
|
+
- Attributes accessed via `$` (e.g., `element.$?.attributeName`)
|
|
153
|
+
- Text content accessed via `_` (e.g., `element._`)
|
|
154
|
+
- Nested elements accessed directly (e.g., `element.childElement`)
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## 🔐 Fluent Commerce Connection Setup
|
|
159
|
+
|
|
160
|
+
**✅ BEST PRACTICE:** Store Fluent Commerce credentials in a Versori connection object:
|
|
161
|
+
|
|
162
|
+
**Connection Configuration:**
|
|
163
|
+
|
|
164
|
+
1. **Create Connection in Versori:**
|
|
165
|
+
- Name: `fluent_commerce`
|
|
166
|
+
- Type: HTTP Basic Auth or OAuth2
|
|
167
|
+
- Credentials: Fluent Commerce API credentials
|
|
168
|
+
|
|
169
|
+
2. **Connection Variables:**
|
|
170
|
+
- `FLUENT_BASE_URL` - Fluent Commerce API base URL (e.g., `https://api.fluentcommerce.com`)
|
|
171
|
+
- `FLUENT_CLIENT_ID` - OAuth client ID (if using OAuth2)
|
|
172
|
+
- `FLUENT_CLIENT_SECRET` - OAuth client secret (if using OAuth2)
|
|
173
|
+
- `FLUENT_USERNAME` - Username (if using Basic Auth)
|
|
174
|
+
- `FLUENT_PASSWORD` - Password (if using Basic Auth)
|
|
175
|
+
|
|
176
|
+
**Benefits:**
|
|
177
|
+
- ✅ Credentials stored securely in Versori vault
|
|
178
|
+
- ✅ Connection can be reused across workflows
|
|
179
|
+
- ✅ No sensitive data in activation variables
|
|
180
|
+
- ✅ Easier credential rotation
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
## 📄 Expected XML Payload Format
|
|
185
|
+
|
|
186
|
+
The webhook accepts **flat XML payloads**. All child elements except `orderRef` are automatically treated as order attributes.
|
|
187
|
+
|
|
188
|
+
### ✅ PRIMARY FORMAT: Flat Structure (Recommended)
|
|
189
|
+
|
|
190
|
+
**Simple format - just child elements, no nested attributes structure:**
|
|
191
|
+
|
|
192
|
+
```xml
|
|
193
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
194
|
+
<orderUpdate>
|
|
195
|
+
<orderRef>ORD-12345</orderRef>
|
|
196
|
+
<externalOrderId>EXT-123456789</externalOrderId>
|
|
197
|
+
<customerNotes>Please leave package at front door</customerNotes>
|
|
198
|
+
<priority>high</priority>
|
|
199
|
+
<tags>rush,gift</tags>
|
|
200
|
+
</orderUpdate>
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
**Type Auto-Detection:**
|
|
204
|
+
- Numbers → `INTEGER` or `FLOAT` (auto-detected)
|
|
205
|
+
- "true"/"false" → `BOOLEAN` (auto-detected)
|
|
206
|
+
- Strings → `STRING` (default)
|
|
207
|
+
|
|
208
|
+
**cURL Example:**
|
|
209
|
+
```bash
|
|
210
|
+
curl -X POST https://{workspace}.versori.run/order-update \
|
|
211
|
+
-H "Content-Type: application/xml" \
|
|
212
|
+
-H "X-API-Key: your-api-key" \
|
|
213
|
+
-d '<?xml version="1.0" encoding="UTF-8"?><orderUpdate><orderRef>ORD-12345</orderRef><externalOrderId>EXT-123456789</externalOrderId><customerNotes>Please leave package at front door</customerNotes><priority>high</priority></orderUpdate>'
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### Complete Sample Payloads
|
|
217
|
+
|
|
218
|
+
**Example 1: External System Reference Update**
|
|
219
|
+
```xml
|
|
220
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
221
|
+
<orderUpdate>
|
|
222
|
+
<orderRef>ORD-0017326966182</orderRef>
|
|
223
|
+
<externalOrderId>EXT-123456789</externalOrderId>
|
|
224
|
+
<sourceSystem>ERP-SAP</sourceSystem>
|
|
225
|
+
<integrationId>INT-987654321</integrationId>
|
|
226
|
+
</orderUpdate>
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
**Example 2: Customer Notes and Business Flags**
|
|
230
|
+
```xml
|
|
231
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
232
|
+
<orderUpdate>
|
|
233
|
+
<orderRef>ORD-0017326966182</orderRef>
|
|
234
|
+
<customerNotes>Please leave package at front door</customerNotes>
|
|
235
|
+
<priority>high</priority>
|
|
236
|
+
<tags>rush,gift</tags>
|
|
237
|
+
<specialHandling>true</specialHandling>
|
|
238
|
+
</orderUpdate>
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
**Note:** Metadata fields (`updateReason`, `updatedBy`, `timestamp`) are optional and excluded from attributes. All other child elements are treated as order attributes.
|
|
242
|
+
|
|
243
|
+
**Versori XML Parsing Format (CRITICAL):**
|
|
244
|
+
|
|
245
|
+
When Versori receives XML with `Content-Type: application/xml`, it **automatically parses** the XML into a JavaScript object. You do NOT need to manually parse XML - Versori handles it automatically.
|
|
246
|
+
|
|
247
|
+
**Access Patterns:**
|
|
248
|
+
- **Attributes**: `element.$?.attributeName` (e.g., `<orderUpdate orderRef="ORD-12345"/>` → `orderUpdate.$?.orderRef`)
|
|
249
|
+
- **Text content**: `element._` or direct property access (e.g., `<orderRef>ORD-12345</orderRef>` → `orderRef._` or `orderRef`)
|
|
250
|
+
- **Nested elements**: Direct property access (e.g., `<orderUpdate><orderRef>...</orderRef></orderUpdate>` → `orderUpdate.orderRef`)
|
|
251
|
+
|
|
252
|
+
**Examples:**
|
|
253
|
+
|
|
254
|
+
```typescript
|
|
255
|
+
// XML: <orderUpdate orderRef="ORD-12345"/>
|
|
256
|
+
// Versori auto-parsed: { orderUpdate: { $: { orderRef: "ORD-12345" } } }
|
|
257
|
+
const orderRef = parsed.orderUpdate?.$?.orderRef; // ✅ "ORD-12345"
|
|
258
|
+
|
|
259
|
+
// XML: <orderRef>ORD-12345</orderRef>
|
|
260
|
+
// Versori auto-parsed: { orderRef: { _: "ORD-12345" } } OR { orderRef: "ORD-12345" }
|
|
261
|
+
const orderRef = parsed.orderRef?._ || parsed.orderRef; // ✅ "ORD-12345"
|
|
262
|
+
|
|
263
|
+
// XML: <attribute name="status" value="processing"/>
|
|
264
|
+
// Versori auto-parsed: { attribute: { $: { name: "status", value: "processing" } } }
|
|
265
|
+
const attrName = parsed.attribute?.$?.name; // ✅ "status"
|
|
266
|
+
const attrValue = parsed.attribute?.$?.value; // ✅ "processing"
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
**Key Point:** Versori automatically parses XML when `Content-Type: application/xml` is set. Your webhook receives `ctx.data` as a pre-parsed JavaScript object - no manual XML parsing needed!
|
|
270
|
+
|
|
271
|
+
---
|
|
272
|
+
|
|
273
|
+
## GraphQL Mutations
|
|
274
|
+
|
|
275
|
+
### Query Order by Reference
|
|
276
|
+
|
|
277
|
+
```graphql
|
|
278
|
+
query GetOrder($ref: String!) {
|
|
279
|
+
order(ref: $ref) {
|
|
280
|
+
id
|
|
281
|
+
ref
|
|
282
|
+
status
|
|
283
|
+
attributes {
|
|
284
|
+
name
|
|
285
|
+
type
|
|
286
|
+
value
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
### Update Order Mutation
|
|
293
|
+
|
|
294
|
+
```graphql
|
|
295
|
+
mutation UpdateOrder($input: UpdateOrderInput!) {
|
|
296
|
+
updateOrder(input: $input) {
|
|
297
|
+
id
|
|
298
|
+
ref
|
|
299
|
+
status
|
|
300
|
+
attributes {
|
|
301
|
+
name
|
|
302
|
+
type
|
|
303
|
+
value
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
**UpdateOrderInput Structure:**
|
|
310
|
+
- `id` (ID!) - Order ID (required) - Must use Fluent internal ID, not reference
|
|
311
|
+
- `attributes` (Array<AttributeInput>) - Attributes to update
|
|
312
|
+
- `status` (String) - Optional status update
|
|
313
|
+
- Other optional fields as per schema
|
|
314
|
+
|
|
315
|
+
**Note:** UpdateOrderInput requires `id: ID!` (the Fluent internal ID), not `ref`. This is why the template queries the order first to get the ID.
|
|
316
|
+
|
|
317
|
+
**AttributeInput Structure:**
|
|
318
|
+
- `name` (String!) - Attribute name
|
|
319
|
+
- `type` (String!) - Attribute type (STRING, INTEGER, FLOAT, BOOLEAN, JSON, DATE)
|
|
320
|
+
- `value` (String!) - Attribute value (stringified for non-STRING types)
|
|
321
|
+
|
|
322
|
+
---
|
|
323
|
+
|
|
324
|
+
## 🔧 Complete Production Code
|
|
325
|
+
|
|
326
|
+
### Versori Workflows Structure
|
|
327
|
+
|
|
328
|
+
**Key Concept**: Versori workflows are organized by **trigger type** at the first level, then by **specific workflow** with descriptive file names.
|
|
329
|
+
|
|
330
|
+
**Trigger Types:**
|
|
331
|
+
- **`webhook()`** → HTTP-based triggers (event-driven) - Creates HTTP endpoints
|
|
332
|
+
|
|
333
|
+
**Execution Steps (chained to triggers):**
|
|
334
|
+
- **`http()`** → External API calls (chained from webhook)
|
|
335
|
+
|
|
336
|
+
### Recommended Project Structure
|
|
337
|
+
|
|
338
|
+
```
|
|
339
|
+
order-update-webhook/
|
|
340
|
+
├── index.ts # Entry point - exports all workflows
|
|
341
|
+
└── src/
|
|
342
|
+
├── workflows/
|
|
343
|
+
│ └── webhook/
|
|
344
|
+
│ └── order-update.ts # Webhook: Order update handler
|
|
345
|
+
│
|
|
346
|
+
├── services/
|
|
347
|
+
│ └── order-update.service.ts # Shared orchestration logic
|
|
348
|
+
│
|
|
349
|
+
├── utils/
|
|
350
|
+
│ └── response-status.utils.ts # FC status code mapping utilities
|
|
351
|
+
│
|
|
352
|
+
└── types/
|
|
353
|
+
└── order-update.types.ts # Type definitions
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
**Benefits:**
|
|
357
|
+
- ✅ Clear structure (webhook handlers in `webhook/`)
|
|
358
|
+
- ✅ Descriptive file names (easy to browse and understand)
|
|
359
|
+
- ✅ Reusable code in `services/` (DRY principle)
|
|
360
|
+
- ✅ Centralized type definitions
|
|
361
|
+
- ✅ Utility functions for consistent error handling (FC status codes)
|
|
362
|
+
|
|
363
|
+
---
|
|
364
|
+
|
|
365
|
+
## Workflow Files
|
|
366
|
+
|
|
367
|
+
### 1. Webhook Workflow (`src/workflows/webhook/order-update.ts`)
|
|
368
|
+
|
|
369
|
+
**Purpose**: Handle order update requests via webhook
|
|
370
|
+
**Trigger**: HTTP POST
|
|
371
|
+
**Endpoint**: `POST https://{workspace}.versori.run/order-update`
|
|
372
|
+
**Use Cases**: Order attribute updates from external systems, status changes
|
|
373
|
+
|
|
374
|
+
```typescript
|
|
375
|
+
import { webhook, http } from '@versori/run';
|
|
376
|
+
import { executeOrderUpdate } from '../../services/order-update.service';
|
|
377
|
+
import { mapErrorToFCStatus } from '../../utils/response-status.utils';
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Webhook: Order Update Handler
|
|
381
|
+
*
|
|
382
|
+
* Endpoint: POST https://{workspace}.versori.run/order-update
|
|
383
|
+
* Request body: XML with orderRef and flat attribute elements
|
|
384
|
+
* Content-Type: application/xml
|
|
385
|
+
*
|
|
386
|
+
* Pattern: webhook().then(http()) - needs Fluent API access
|
|
387
|
+
* Uses shared service: order-update.service.ts
|
|
388
|
+
*
|
|
389
|
+
* SECURITY: Authentication handled via connection parameter
|
|
390
|
+
* No manual API key validation needed - Versori manages this via connection auth
|
|
391
|
+
*/
|
|
392
|
+
export const orderUpdateWebhook = webhook('order-update', {
|
|
393
|
+
response: { mode: 'sync' }, // ✅ Sync mode: response sent when handler returns
|
|
394
|
+
connection: 'order-update-webhook', // Versori validates API key
|
|
395
|
+
}).then(
|
|
396
|
+
http('process-order-update', { connection: 'fluent_commerce' }, async ctx => {
|
|
397
|
+
const { log, data } = ctx;
|
|
398
|
+
|
|
399
|
+
log.info('🚀 [WEBHOOK] Order update request received', {
|
|
400
|
+
timestamp: new Date().toISOString(),
|
|
401
|
+
hasPayload: !!data,
|
|
402
|
+
contentType: ctx.request?.headers?.get('content-type'),
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
try {
|
|
406
|
+
// Reuse shared orchestration logic
|
|
407
|
+
const result = await executeOrderUpdate(ctx);
|
|
408
|
+
|
|
409
|
+
log.info('✅ [WEBHOOK] Order update completed successfully', {
|
|
410
|
+
orderRef: result.orderRef,
|
|
411
|
+
orderId: result.orderId,
|
|
412
|
+
attributesUpdated: result.attributesUpdated,
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
// Return response with FC200 status code
|
|
416
|
+
return {
|
|
417
|
+
success: true,
|
|
418
|
+
statusCode: result.statusCode,
|
|
419
|
+
orderRef: result.orderRef,
|
|
420
|
+
orderId: result.orderId,
|
|
421
|
+
attributesUpdated: result.attributesUpdated,
|
|
422
|
+
message: result.message || 'Order attributes updated successfully',
|
|
423
|
+
};
|
|
424
|
+
} catch (e: any) {
|
|
425
|
+
log.error('❌ [WEBHOOK] Order update failed', {
|
|
426
|
+
message: e?.message,
|
|
427
|
+
stack: e?.stack,
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
// Map error to FC status code
|
|
431
|
+
const fcStatus = mapErrorToFCStatus(e);
|
|
432
|
+
|
|
433
|
+
return {
|
|
434
|
+
success: false,
|
|
435
|
+
statusCode: fcStatus.statusCode,
|
|
436
|
+
retryable: fcStatus.retryable,
|
|
437
|
+
error: fcStatus.message,
|
|
438
|
+
details: e?.message || 'Unknown error occurred',
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
})
|
|
442
|
+
);
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
---
|
|
446
|
+
|
|
447
|
+
### 2. Entry Point (`index.ts`)
|
|
448
|
+
|
|
449
|
+
**Purpose**: Register all workflows with Versori platform
|
|
450
|
+
|
|
451
|
+
```typescript
|
|
452
|
+
/**
|
|
453
|
+
* Entry Point - Registers all workflows with Versori platform
|
|
454
|
+
*
|
|
455
|
+
* Versori automatically discovers and registers exported workflows
|
|
456
|
+
*
|
|
457
|
+
* File Structure:
|
|
458
|
+
* - src/workflows/webhook/ → HTTP-based triggers (webhooks)
|
|
459
|
+
* - src/services/ → Shared service logic (reusable across workflows)
|
|
460
|
+
*/
|
|
461
|
+
|
|
462
|
+
// Import webhook workflows
|
|
463
|
+
import { orderUpdateWebhook } from './src/workflows/webhook/order-update';
|
|
464
|
+
|
|
465
|
+
// Register all workflows with Versori platform
|
|
466
|
+
export {
|
|
467
|
+
// Webhooks (HTTP-based triggers)
|
|
468
|
+
orderUpdateWebhook,
|
|
469
|
+
};
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
**What Gets Exposed:**
|
|
473
|
+
- ✅ `orderUpdateWebhook` → `https://{workspace}.versori.run/order-update`
|
|
474
|
+
|
|
475
|
+
---
|
|
476
|
+
|
|
477
|
+
## 3. Type Definitions (`src/types/order-update.types.ts`)
|
|
478
|
+
|
|
479
|
+
```typescript
|
|
480
|
+
/**
|
|
481
|
+
* Type Definitions for Order Update Ingestion
|
|
482
|
+
*
|
|
483
|
+
* Centralized type definitions for order update workflow
|
|
484
|
+
*/
|
|
485
|
+
|
|
486
|
+
/**
|
|
487
|
+
* Versori XML parsed format
|
|
488
|
+
* Attributes accessed via $, text content via _
|
|
489
|
+
*/
|
|
490
|
+
export interface VersoriXMLParsed {
|
|
491
|
+
[key: string]: unknown;
|
|
492
|
+
$?: Record<string, unknown>; // Attributes
|
|
493
|
+
_?: string; // Text content
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* Order update request payload (XML parsed format)
|
|
498
|
+
*/
|
|
499
|
+
export interface OrderUpdatePayload {
|
|
500
|
+
orderUpdate?: {
|
|
501
|
+
orderRef?: VersoriXMLParsed | string;
|
|
502
|
+
attributes?: {
|
|
503
|
+
attribute?: VersoriXMLParsed | Array<VersoriXMLParsed>;
|
|
504
|
+
};
|
|
505
|
+
$?: {
|
|
506
|
+
orderRef?: string;
|
|
507
|
+
};
|
|
508
|
+
[key: string]: VersoriXMLParsed | string | unknown; // Flat attributes
|
|
509
|
+
};
|
|
510
|
+
order?: {
|
|
511
|
+
ref?: VersoriXMLParsed | string;
|
|
512
|
+
$?: {
|
|
513
|
+
ref?: string;
|
|
514
|
+
};
|
|
515
|
+
};
|
|
516
|
+
[key: string]: unknown; // Allow additional fields
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
/**
|
|
520
|
+
* Fluent AttributeInput format
|
|
521
|
+
*/
|
|
522
|
+
export interface AttributeInput {
|
|
523
|
+
name: string;
|
|
524
|
+
type: 'STRING' | 'INTEGER' | 'FLOAT' | 'BOOLEAN' | 'JSON' | 'DATE';
|
|
525
|
+
value: string;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
/**
|
|
529
|
+
* Order query result
|
|
530
|
+
*/
|
|
531
|
+
export interface OrderQueryResult {
|
|
532
|
+
id: string;
|
|
533
|
+
ref: string;
|
|
534
|
+
status?: string;
|
|
535
|
+
attributes?: Array<{
|
|
536
|
+
name: string;
|
|
537
|
+
type: string;
|
|
538
|
+
value: string;
|
|
539
|
+
}>;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
/**
|
|
543
|
+
* Order update result with FC status code
|
|
544
|
+
*/
|
|
545
|
+
export interface OrderUpdateResult {
|
|
546
|
+
success: boolean;
|
|
547
|
+
statusCode: 'FC200' | 'FC400' | 'FC409' | 'FC504';
|
|
548
|
+
orderRef: string;
|
|
549
|
+
orderId: string;
|
|
550
|
+
attributesUpdated: number;
|
|
551
|
+
message?: string;
|
|
552
|
+
error?: string;
|
|
553
|
+
retryable?: boolean;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
/**
|
|
557
|
+
* Versori Context Interface
|
|
558
|
+
* Represents the Versori runtime context passed to workflow functions
|
|
559
|
+
*/
|
|
560
|
+
export interface VersoriContext {
|
|
561
|
+
log: {
|
|
562
|
+
info: (message: string, data?: Record<string, unknown>) => void;
|
|
563
|
+
warn: (message: string, data?: Record<string, unknown>) => void;
|
|
564
|
+
error: (message: string, data?: Record<string, unknown>) => void;
|
|
565
|
+
debug?: (message: string, data?: Record<string, unknown>) => void;
|
|
566
|
+
};
|
|
567
|
+
data?: unknown;
|
|
568
|
+
request?: Request;
|
|
569
|
+
activation: {
|
|
570
|
+
getVariable: (name: string) => string | undefined;
|
|
571
|
+
connections?: Record<string, unknown>;
|
|
572
|
+
};
|
|
573
|
+
connections?: Record<string, unknown>;
|
|
574
|
+
}
|
|
575
|
+
```
|
|
576
|
+
|
|
577
|
+
---
|
|
578
|
+
|
|
579
|
+
## 4. Utility: Response Status Code Mapper (`src/utils/response-status.utils.ts`)
|
|
580
|
+
|
|
581
|
+
```typescript
|
|
582
|
+
/**
|
|
583
|
+
* Response Status Code Utility
|
|
584
|
+
*
|
|
585
|
+
* Maps errors to Fluent Commerce (FC) status codes for consistent API responses
|
|
586
|
+
*/
|
|
587
|
+
|
|
588
|
+
/**
|
|
589
|
+
* FC Status Codes:
|
|
590
|
+
* - FC200: OK (Successfully - No further action needed)
|
|
591
|
+
* - FC400: Bad Request (Non-retryable)
|
|
592
|
+
* - FC409: Duplicate Request, Entity already exists (Non-retryable)
|
|
593
|
+
* - FC504: Timeout (Retryable)
|
|
594
|
+
*/
|
|
595
|
+
|
|
596
|
+
export type FCStatusCode = 'FC200' | 'FC400' | 'FC409' | 'FC504';
|
|
597
|
+
|
|
598
|
+
export interface FCStatusMapping {
|
|
599
|
+
statusCode: FCStatusCode;
|
|
600
|
+
retryable: boolean;
|
|
601
|
+
message: string;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
/**
|
|
605
|
+
* Map error to FC status code based on error message and type
|
|
606
|
+
*/
|
|
607
|
+
export function mapErrorToFCStatus(error: unknown): FCStatusMapping {
|
|
608
|
+
const errorMessage =
|
|
609
|
+
error instanceof Error ? error.message : String(error);
|
|
610
|
+
const lowerMessage = errorMessage.toLowerCase();
|
|
611
|
+
|
|
612
|
+
// FC409: Duplicate/Already exists (Non-retryable)
|
|
613
|
+
if (
|
|
614
|
+
lowerMessage.includes('already exists') ||
|
|
615
|
+
lowerMessage.includes('duplicate') ||
|
|
616
|
+
lowerMessage.includes('unique constraint') ||
|
|
617
|
+
lowerMessage.includes('entity already exists')
|
|
618
|
+
) {
|
|
619
|
+
return {
|
|
620
|
+
statusCode: 'FC409',
|
|
621
|
+
retryable: false,
|
|
622
|
+
message: 'Entity already exists. Duplicate request detected.',
|
|
623
|
+
};
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
// FC504: Timeout (Retryable)
|
|
627
|
+
if (
|
|
628
|
+
lowerMessage.includes('timeout') ||
|
|
629
|
+
lowerMessage.includes('timed out') ||
|
|
630
|
+
lowerMessage.includes('request timeout') ||
|
|
631
|
+
(error instanceof Error && error.name === 'TimeoutError')
|
|
632
|
+
) {
|
|
633
|
+
return {
|
|
634
|
+
statusCode: 'FC504',
|
|
635
|
+
retryable: true,
|
|
636
|
+
message: 'Request timeout. Please retry.',
|
|
637
|
+
};
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
// FC400: Bad Request (Non-retryable)
|
|
641
|
+
// Validation errors, missing fields, invalid format
|
|
642
|
+
if (
|
|
643
|
+
lowerMessage.includes('required') ||
|
|
644
|
+
lowerMessage.includes('missing') ||
|
|
645
|
+
lowerMessage.includes('invalid') ||
|
|
646
|
+
lowerMessage.includes('bad request') ||
|
|
647
|
+
lowerMessage.includes('validation') ||
|
|
648
|
+
lowerMessage.includes('not found') ||
|
|
649
|
+
lowerMessage.includes('does not exist')
|
|
650
|
+
) {
|
|
651
|
+
return {
|
|
652
|
+
statusCode: 'FC400',
|
|
653
|
+
retryable: false,
|
|
654
|
+
message: 'Bad request. Please verify payload and try again.',
|
|
655
|
+
};
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
// Default: FC400 for unknown errors (Non-retryable)
|
|
659
|
+
return {
|
|
660
|
+
statusCode: 'FC400',
|
|
661
|
+
retryable: false,
|
|
662
|
+
message: errorMessage,
|
|
663
|
+
};
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
/**
|
|
667
|
+
* Create success response with FC200 status
|
|
668
|
+
*/
|
|
669
|
+
export function createSuccessResponse<T extends Record<string, unknown>>(
|
|
670
|
+
data: T
|
|
671
|
+
): T & { statusCode: 'FC200'; retryable: false } {
|
|
672
|
+
return {
|
|
673
|
+
...data,
|
|
674
|
+
statusCode: 'FC200' as const,
|
|
675
|
+
retryable: false,
|
|
676
|
+
};
|
|
677
|
+
}
|
|
678
|
+
```
|
|
679
|
+
|
|
680
|
+
---
|
|
681
|
+
|
|
682
|
+
## 5. Service: Order Update Processor (`src/services/order-update.service.ts`)
|
|
683
|
+
|
|
684
|
+
```typescript
|
|
685
|
+
/**
|
|
686
|
+
* Order Update Service
|
|
687
|
+
*
|
|
688
|
+
* Processes order update requests: extracts order reference and attributes from XML payload,
|
|
689
|
+
* queries Fluent Commerce to get order ID, and updates order attributes via GraphQL mutation.
|
|
690
|
+
* Handles Versori XML parsing format ($ for attributes, _ for text).
|
|
691
|
+
*/
|
|
692
|
+
|
|
693
|
+
import {
|
|
694
|
+
createClient,
|
|
695
|
+
XMLParserService,
|
|
696
|
+
} from '@fluentcommerce/fc-connect-sdk';
|
|
697
|
+
import { Buffer } from 'node:buffer';
|
|
698
|
+
import { mapErrorToFCStatus, createSuccessResponse } from '../utils/response-status.utils';
|
|
699
|
+
import type { FluentClient } from '@fluentcommerce/fc-connect-sdk';
|
|
700
|
+
import type {
|
|
701
|
+
OrderUpdatePayload,
|
|
702
|
+
OrderUpdateResult,
|
|
703
|
+
VersoriContext,
|
|
704
|
+
AttributeInput,
|
|
705
|
+
OrderQueryResult,
|
|
706
|
+
VersoriXMLParsed,
|
|
707
|
+
} from '../types/order-update.types';
|
|
708
|
+
|
|
709
|
+
/**
|
|
710
|
+
* GraphQL query to get order by reference
|
|
711
|
+
*/
|
|
712
|
+
const GET_ORDER_QUERY = `
|
|
713
|
+
query GetOrder($ref: String!) {
|
|
714
|
+
order(ref: $ref) {
|
|
715
|
+
id
|
|
716
|
+
ref
|
|
717
|
+
status
|
|
718
|
+
attributes {
|
|
719
|
+
name
|
|
720
|
+
type
|
|
721
|
+
value
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
`;
|
|
726
|
+
|
|
727
|
+
/**
|
|
728
|
+
* GraphQL mutation to update order
|
|
729
|
+
*/
|
|
730
|
+
const UPDATE_ORDER_MUTATION = `
|
|
731
|
+
mutation UpdateOrder($input: UpdateOrderInput!) {
|
|
732
|
+
updateOrder(input: $input) {
|
|
733
|
+
id
|
|
734
|
+
ref
|
|
735
|
+
status
|
|
736
|
+
attributes {
|
|
737
|
+
name
|
|
738
|
+
type
|
|
739
|
+
value
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
`;
|
|
744
|
+
|
|
745
|
+
/**
|
|
746
|
+
* Extract text content from Versori XML parsed format
|
|
747
|
+
* Handles both object format ({ _: "text" }) and direct string
|
|
748
|
+
*/
|
|
749
|
+
function extractTextContent(value: VersoriXMLParsed | string | undefined): string | null {
|
|
750
|
+
if (!value) {
|
|
751
|
+
return null;
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
if (typeof value === 'string') {
|
|
755
|
+
return value;
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
if (typeof value === 'object' && '_' in value) {
|
|
759
|
+
const text = value._;
|
|
760
|
+
return typeof text === 'string' ? text : null;
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
return null;
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
/**
|
|
767
|
+
* Extract order reference from XML payload
|
|
768
|
+
*
|
|
769
|
+
* Supports multiple XML formats:
|
|
770
|
+
* - <orderUpdate><orderRef>ORD-12345</orderRef></orderUpdate>
|
|
771
|
+
* - <orderUpdate orderRef="ORD-12345"/>
|
|
772
|
+
* - <order ref="ORD-12345"/>
|
|
773
|
+
*/
|
|
774
|
+
function extractOrderRef(payload: OrderUpdatePayload): string | null {
|
|
775
|
+
// Try orderUpdate.orderRef (text content)
|
|
776
|
+
if (payload.orderUpdate) {
|
|
777
|
+
const orderUpdate = payload.orderUpdate;
|
|
778
|
+
|
|
779
|
+
// Try orderUpdate.orderRef as text
|
|
780
|
+
if (orderUpdate.orderRef) {
|
|
781
|
+
const ref = extractTextContent(orderUpdate.orderRef);
|
|
782
|
+
if (ref) {
|
|
783
|
+
return ref;
|
|
784
|
+
}
|
|
785
|
+
if (typeof orderUpdate.orderRef === 'string') {
|
|
786
|
+
return orderUpdate.orderRef;
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
// Try orderUpdate.$?.orderRef (root attribute)
|
|
791
|
+
if ('$' in orderUpdate && orderUpdate.$) {
|
|
792
|
+
const attrs = orderUpdate.$ as Record<string, unknown>;
|
|
793
|
+
if (attrs && typeof attrs.orderRef === 'string') {
|
|
794
|
+
return attrs.orderRef;
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
// Try direct order element
|
|
800
|
+
if (payload.order) {
|
|
801
|
+
const order = payload.order;
|
|
802
|
+
|
|
803
|
+
// Try order.$?.ref (attribute)
|
|
804
|
+
if (typeof order === 'object' && '$' in order && order.$) {
|
|
805
|
+
const attrs = order.$ as Record<string, unknown>;
|
|
806
|
+
if (attrs && typeof attrs.ref === 'string') {
|
|
807
|
+
return attrs.ref;
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
// Try order.ref (text content)
|
|
812
|
+
if (typeof order === 'object' && 'ref' in order) {
|
|
813
|
+
const ref = extractTextContent(order.ref as VersoriXMLParsed);
|
|
814
|
+
if (ref) {
|
|
815
|
+
return ref;
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
return null;
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
/**
|
|
824
|
+
* Extract attributes from XML payload
|
|
825
|
+
*
|
|
826
|
+
* PRIMARY FORMAT: Flat structure - all direct children of orderUpdate (except orderRef and metadata)
|
|
827
|
+
* - <orderUpdate><orderRef>ORD-123</orderRef><externalOrderId>EXT-123</externalOrderId></orderUpdate>
|
|
828
|
+
*
|
|
829
|
+
* Supports multiple XML formats:
|
|
830
|
+
* - PRIMARY: Flat structure: <externalOrderId>...</externalOrderId>
|
|
831
|
+
* - ALTERNATIVE: <attributes><attribute><name>...</name><value>...</value></attribute></attributes>
|
|
832
|
+
* - ALTERNATIVE: <attributes><attribute name="..." value="..."/></attributes>
|
|
833
|
+
*/
|
|
834
|
+
function extractAttributes(payload: OrderUpdatePayload): AttributeInput[] {
|
|
835
|
+
const attributes: AttributeInput[] = [];
|
|
836
|
+
const metadataFields = ['orderRef', 'attributes', '$', '_', 'updateReason', 'updatedBy', 'timestamp'];
|
|
837
|
+
|
|
838
|
+
// ✅ PRIMARY FORMAT: Flat structure - all direct children of orderUpdate (except orderRef and metadata)
|
|
839
|
+
// Example: <orderUpdate><orderRef>ORD-123</orderRef><externalOrderId>EXT-123</externalOrderId><customerNotes>Leave at door</customerNotes></orderUpdate>
|
|
840
|
+
if (payload.orderUpdate && typeof payload.orderUpdate === 'object') {
|
|
841
|
+
for (const [key, value] of Object.entries(payload.orderUpdate)) {
|
|
842
|
+
// Skip metadata fields
|
|
843
|
+
if (metadataFields.includes(key)) {
|
|
844
|
+
continue;
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
const textValue = extractTextContent(value as VersoriXMLParsed);
|
|
848
|
+
if (textValue !== null && textValue !== undefined) {
|
|
849
|
+
// Auto-detect type from value (basic detection for XML - mostly STRING)
|
|
850
|
+
// For more complex types, use the attributes format
|
|
851
|
+
let type: AttributeInput['type'] = 'STRING';
|
|
852
|
+
|
|
853
|
+
// Try to detect number
|
|
854
|
+
const numValue = Number(textValue);
|
|
855
|
+
if (!isNaN(numValue) && textValue.trim() !== '') {
|
|
856
|
+
type = Number.isInteger(numValue) ? 'INTEGER' : 'FLOAT';
|
|
857
|
+
}
|
|
858
|
+
// Try to detect boolean
|
|
859
|
+
else if (textValue.toLowerCase() === 'true' || textValue.toLowerCase() === 'false') {
|
|
860
|
+
type = 'BOOLEAN';
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
attributes.push({
|
|
864
|
+
name: key,
|
|
865
|
+
type,
|
|
866
|
+
value: textValue,
|
|
867
|
+
});
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
// ALTERNATIVE FORMAT: orderUpdate.attributes.attribute (nested structure)
|
|
873
|
+
// Only used if flat structure didn't yield any attributes
|
|
874
|
+
if (attributes.length === 0 && payload.orderUpdate?.attributes?.attribute) {
|
|
875
|
+
const attrArray = Array.isArray(payload.orderUpdate.attributes.attribute)
|
|
876
|
+
? payload.orderUpdate.attributes.attribute
|
|
877
|
+
: [payload.orderUpdate.attributes.attribute];
|
|
878
|
+
|
|
879
|
+
for (const attr of attrArray) {
|
|
880
|
+
if (typeof attr === 'object') {
|
|
881
|
+
// Element format: <attribute><name>...</name><value>...</value></attribute>
|
|
882
|
+
const name = extractTextContent(attr.name as VersoriXMLParsed) ||
|
|
883
|
+
(attr.$ as Record<string, unknown>)?.name as string;
|
|
884
|
+
const value = extractTextContent(attr.value as VersoriXMLParsed) ||
|
|
885
|
+
(attr.$ as Record<string, unknown>)?.value as string;
|
|
886
|
+
const type = (extractTextContent(attr.type as VersoriXMLParsed) ||
|
|
887
|
+
(attr.$ as Record<string, unknown>)?.type as string || 'STRING') as AttributeInput['type'];
|
|
888
|
+
|
|
889
|
+
if (name && value !== null && value !== undefined) {
|
|
890
|
+
attributes.push({
|
|
891
|
+
name: String(name),
|
|
892
|
+
type: type || 'STRING',
|
|
893
|
+
value: String(value),
|
|
894
|
+
});
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
return attributes;
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
/**
|
|
904
|
+
* Parse webhook payload from Versori context
|
|
905
|
+
*
|
|
906
|
+
* CRITICAL: Versori automatically parses XML when Content-Type: application/xml is set.
|
|
907
|
+
* The ctx.data will be a pre-parsed JavaScript object - no manual parsing needed!
|
|
908
|
+
*
|
|
909
|
+
* Handles both pre-parsed XML (ctx.data) and raw request body (ctx.request) as fallback
|
|
910
|
+
*/
|
|
911
|
+
async function parseWebhookPayload(
|
|
912
|
+
ctx: VersoriContext
|
|
913
|
+
): Promise<OrderUpdatePayload | null> {
|
|
914
|
+
const { log, data, request } = ctx;
|
|
915
|
+
|
|
916
|
+
// ✅ Scenario 1: Pre-parsed XML (Versori auto-parsed when Content-Type: application/xml)
|
|
917
|
+
// This is the PRIMARY path - Versori handles XML parsing automatically
|
|
918
|
+
if (data && typeof data === 'object' && data !== null) {
|
|
919
|
+
log.info('📥 [PARSE] Using Versori auto-parsed XML payload', {
|
|
920
|
+
hasData: true,
|
|
921
|
+
dataKeys: Object.keys(data),
|
|
922
|
+
contentType: request?.headers?.get('content-type'),
|
|
923
|
+
});
|
|
924
|
+
return data as OrderUpdatePayload;
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
// ⚠️ Scenario 2: Raw request body (fallback - should rarely be needed)
|
|
928
|
+
// Only used if Versori didn't auto-parse (e.g., wrong Content-Type header)
|
|
929
|
+
if (request && typeof request.text === 'function') {
|
|
930
|
+
log.warn('📥 [PARSE] Falling back to manual XML parsing (Content-Type may be incorrect)');
|
|
931
|
+
try {
|
|
932
|
+
const rawBody = await request.text();
|
|
933
|
+
if (!rawBody || rawBody.trim() === '') {
|
|
934
|
+
log.error('❌ [PARSE] Request body is empty');
|
|
935
|
+
return null;
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
const parser = new XMLParserService(log);
|
|
939
|
+
const parsed = await parser.parse(rawBody);
|
|
940
|
+
|
|
941
|
+
log.info('📥 [PARSE] Successfully parsed XML payload', {
|
|
942
|
+
parsedKeys: Object.keys(parsed),
|
|
943
|
+
});
|
|
944
|
+
|
|
945
|
+
return parsed as OrderUpdatePayload;
|
|
946
|
+
} catch (parseError: unknown) {
|
|
947
|
+
const errorMessage =
|
|
948
|
+
parseError instanceof Error ? parseError.message : String(parseError);
|
|
949
|
+
log.error('❌ [PARSE] Failed to parse XML payload', {
|
|
950
|
+
error: errorMessage,
|
|
951
|
+
});
|
|
952
|
+
return null;
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
log.error('❌ [PARSE] No valid payload found in context');
|
|
957
|
+
return null;
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
/**
|
|
961
|
+
* Query order by reference to get ID and current state
|
|
962
|
+
*/
|
|
963
|
+
async function queryOrderByRef(
|
|
964
|
+
client: FluentClient,
|
|
965
|
+
orderRef: string,
|
|
966
|
+
log: VersoriContext['log']
|
|
967
|
+
): Promise<OrderQueryResult> {
|
|
968
|
+
log.info('🔍 [QUERY] Querying order by reference', { orderRef });
|
|
969
|
+
|
|
970
|
+
const result = await client.graphql({
|
|
971
|
+
query: GET_ORDER_QUERY,
|
|
972
|
+
variables: { ref: orderRef },
|
|
973
|
+
});
|
|
974
|
+
|
|
975
|
+
if (result.errors && result.errors.length > 0) {
|
|
976
|
+
log.error('❌ [QUERY] GraphQL query returned errors', {
|
|
977
|
+
errors: result.errors,
|
|
978
|
+
orderRef,
|
|
979
|
+
});
|
|
980
|
+
throw new Error(`GraphQL query failed: ${result.errors[0].message}`);
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
const order = (result.data as any)?.order;
|
|
984
|
+
|
|
985
|
+
if (!order) {
|
|
986
|
+
log.error('❌ [QUERY] Order not found', { orderRef });
|
|
987
|
+
throw new Error(`Order not found: ${orderRef}`);
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
log.info('✅ [QUERY] Order found', {
|
|
991
|
+
orderId: order.id,
|
|
992
|
+
orderRef: order.ref,
|
|
993
|
+
status: order.status,
|
|
994
|
+
currentAttributeCount: order.attributes?.length || 0,
|
|
995
|
+
});
|
|
996
|
+
|
|
997
|
+
return order as OrderQueryResult;
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
/**
|
|
1001
|
+
* Execute order update workflow
|
|
1002
|
+
*
|
|
1003
|
+
* Main orchestration function:
|
|
1004
|
+
* 1. Parse webhook payload
|
|
1005
|
+
* 2. Extract order reference and attributes
|
|
1006
|
+
* 3. Create Fluent client
|
|
1007
|
+
* 4. Query order by reference to get ID
|
|
1008
|
+
* 5. Build update mutation variables
|
|
1009
|
+
* 6. Execute updateOrder mutation
|
|
1010
|
+
*
|
|
1011
|
+
* Note: Authentication handled via Versori connection (fluent_commerce).
|
|
1012
|
+
* Retailer ID is not required for GraphQL mutations - connection handles authentication.
|
|
1013
|
+
*
|
|
1014
|
+
* CRITICAL: UpdateOrderInput requires id: ID! (not ref).
|
|
1015
|
+
* The query step retrieves order.id which must be used in the mutation.
|
|
1016
|
+
*/
|
|
1017
|
+
export async function executeOrderUpdate(
|
|
1018
|
+
ctx: VersoriContext
|
|
1019
|
+
): Promise<OrderUpdateResult> {
|
|
1020
|
+
const { log, activation } = ctx;
|
|
1021
|
+
|
|
1022
|
+
log.info('🚀 [SERVICE] Starting order update processing');
|
|
1023
|
+
|
|
1024
|
+
// STEP 1: Parse webhook payload
|
|
1025
|
+
// ✅ Versori automatically parses XML when Content-Type: application/xml is set
|
|
1026
|
+
// ctx.data will be a pre-parsed JavaScript object - no manual parsing needed!
|
|
1027
|
+
const payload = await parseWebhookPayload(ctx);
|
|
1028
|
+
if (!payload) {
|
|
1029
|
+
log.error('❌ [SERVICE] Failed to parse webhook payload', {
|
|
1030
|
+
hasData: !!ctx.data,
|
|
1031
|
+
hasRequest: !!ctx.request,
|
|
1032
|
+
});
|
|
1033
|
+
throw new Error(
|
|
1034
|
+
'Failed to parse webhook payload. Ensure Content-Type: application/xml is set in request headers.'
|
|
1035
|
+
);
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
log.info('📥 [SERVICE] Payload parsed successfully (Versori auto-parsed XML)', {
|
|
1039
|
+
payloadKeys: Object.keys(payload),
|
|
1040
|
+
note: 'Versori automatically parses XML when Content-Type: application/xml is set',
|
|
1041
|
+
});
|
|
1042
|
+
|
|
1043
|
+
// STEP 2: Extract order reference
|
|
1044
|
+
const orderRef = extractOrderRef(payload);
|
|
1045
|
+
if (!orderRef) {
|
|
1046
|
+
log.error('❌ [SERVICE] Order reference not found in XML payload', {
|
|
1047
|
+
payloadKeys: Object.keys(payload),
|
|
1048
|
+
expectedPatterns: [
|
|
1049
|
+
'orderUpdate.orderRef',
|
|
1050
|
+
'orderUpdate[@orderRef]',
|
|
1051
|
+
'order[@ref]',
|
|
1052
|
+
],
|
|
1053
|
+
});
|
|
1054
|
+
throw new Error(
|
|
1055
|
+
'Order reference not found in XML payload. Expected patterns: orderUpdate.orderRef, orderUpdate[@orderRef], or order[@ref]'
|
|
1056
|
+
);
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
log.info('✅ [SERVICE] Order reference extracted', { orderRef });
|
|
1060
|
+
|
|
1061
|
+
// STEP 3: Extract attributes
|
|
1062
|
+
const attributes = extractAttributes(payload);
|
|
1063
|
+
if (attributes.length === 0) {
|
|
1064
|
+
log.warn('⚠️ [SERVICE] No attributes provided in payload', { orderRef });
|
|
1065
|
+
throw new Error('At least one attribute is required for order update');
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
log.info('✅ [SERVICE] Attributes extracted', {
|
|
1069
|
+
orderRef,
|
|
1070
|
+
attributeCount: attributes.length,
|
|
1071
|
+
attributeNames: attributes.map(a => a.name),
|
|
1072
|
+
});
|
|
1073
|
+
|
|
1074
|
+
// STEP 4: Create Fluent client
|
|
1075
|
+
// ✅ Pass ctx directly - createClient auto-detects Versori context
|
|
1076
|
+
// Authentication handled via Versori connection (fluent_commerce)
|
|
1077
|
+
const client = await createClient(ctx);
|
|
1078
|
+
|
|
1079
|
+
log.info('✅ [SERVICE] Fluent client created');
|
|
1080
|
+
|
|
1081
|
+
// STEP 5: Query order by reference to get ID
|
|
1082
|
+
// ✅ Query first to:
|
|
1083
|
+
// - Validate order exists (fail-fast if order not found)
|
|
1084
|
+
// - Get order ID for logging and tracking
|
|
1085
|
+
// - Get current order state (optional: can merge with existing attributes)
|
|
1086
|
+
const order = await queryOrderByRef(client, orderRef, log);
|
|
1087
|
+
|
|
1088
|
+
// STEP 6: Build update mutation variables
|
|
1089
|
+
// ✅ CRITICAL: UpdateOrderInput requires id: ID! (not ref)
|
|
1090
|
+
// Use the ID from queryOrderByRef result - this is why we query first!
|
|
1091
|
+
const updateVariables = {
|
|
1092
|
+
input: {
|
|
1093
|
+
id: order.id, // ✅ REQUIRED: UpdateOrderInput.id is ID! (required field)
|
|
1094
|
+
attributes: attributes,
|
|
1095
|
+
},
|
|
1096
|
+
};
|
|
1097
|
+
|
|
1098
|
+
log.info('📤 [SERVICE] Executing updateOrder mutation', {
|
|
1099
|
+
orderRef,
|
|
1100
|
+
orderId: order.id,
|
|
1101
|
+
attributeCount: attributes.length,
|
|
1102
|
+
});
|
|
1103
|
+
|
|
1104
|
+
// STEP 7: Execute updateOrder mutation
|
|
1105
|
+
const mutationResult = await client.graphql({
|
|
1106
|
+
query: UPDATE_ORDER_MUTATION,
|
|
1107
|
+
variables: updateVariables,
|
|
1108
|
+
});
|
|
1109
|
+
|
|
1110
|
+
if (mutationResult.errors && mutationResult.errors.length > 0) {
|
|
1111
|
+
log.error('❌ [SERVICE] GraphQL mutation returned errors', {
|
|
1112
|
+
errors: mutationResult.errors,
|
|
1113
|
+
orderRef,
|
|
1114
|
+
orderId: order.id,
|
|
1115
|
+
});
|
|
1116
|
+
throw new Error(`GraphQL mutation failed: ${mutationResult.errors[0].message}`);
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
const updatedOrder = (mutationResult.data as any)?.updateOrder;
|
|
1120
|
+
|
|
1121
|
+
if (!updatedOrder) {
|
|
1122
|
+
log.error('❌ [SERVICE] No order data returned from mutation', { orderRef });
|
|
1123
|
+
throw new Error('No order data returned from updateOrder mutation');
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
log.info('✅ [SERVICE] Order updated successfully', {
|
|
1127
|
+
orderRef,
|
|
1128
|
+
orderId: updatedOrder.id,
|
|
1129
|
+
attributesUpdated: attributes.length,
|
|
1130
|
+
});
|
|
1131
|
+
|
|
1132
|
+
// Return success response with FC200 status code
|
|
1133
|
+
return createSuccessResponse({
|
|
1134
|
+
success: true,
|
|
1135
|
+
orderRef,
|
|
1136
|
+
orderId: updatedOrder.id,
|
|
1137
|
+
attributesUpdated: attributes.length,
|
|
1138
|
+
message: 'Order attributes updated successfully',
|
|
1139
|
+
});
|
|
1140
|
+
}
|
|
1141
|
+
```
|
|
1142
|
+
|
|
1143
|
+
---
|
|
1144
|
+
|
|
1145
|
+
## 5. Configuration
|
|
1146
|
+
|
|
1147
|
+
### Activation Variables
|
|
1148
|
+
|
|
1149
|
+
No activation variables required. Authentication is handled via Versori connection (`fluent_commerce`).
|
|
1150
|
+
|
|
1151
|
+
**Note:** Configure Fluent Commerce credentials in the Versori connection settings. The connection handles authentication automatically.
|
|
1152
|
+
|
|
1153
|
+
### Webhook Connection
|
|
1154
|
+
|
|
1155
|
+
Create a Versori connection for webhook authentication:
|
|
1156
|
+
|
|
1157
|
+
1. **Connection Name:** `order-update-webhook`
|
|
1158
|
+
2. **Type:** API Key or Basic Auth
|
|
1159
|
+
3. **Purpose:** Authenticate incoming webhook requests
|
|
1160
|
+
|
|
1161
|
+
**Example webhook call:**
|
|
1162
|
+
|
|
1163
|
+
```bash
|
|
1164
|
+
curl -X POST https://{workspace}.versori.run/order-update \
|
|
1165
|
+
-H "Content-Type: application/xml" \
|
|
1166
|
+
-H "X-API-Key: your-api-key" \
|
|
1167
|
+
--data-binary @order-update.xml
|
|
1168
|
+
```
|
|
1169
|
+
|
|
1170
|
+
**Example XML file (`order-update.xml`):**
|
|
1171
|
+
|
|
1172
|
+
```xml
|
|
1173
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
1174
|
+
<orderUpdate>
|
|
1175
|
+
<orderRef>ORD-12345</orderRef>
|
|
1176
|
+
<externalOrderId>EXT-123456789</externalOrderId>
|
|
1177
|
+
<customerNotes>Please leave package at front door</customerNotes>
|
|
1178
|
+
<priority>high</priority>
|
|
1179
|
+
</orderUpdate>
|
|
1180
|
+
```
|
|
1181
|
+
|
|
1182
|
+
**CRITICAL: Content-Type Header**
|
|
1183
|
+
|
|
1184
|
+
The `Content-Type: application/xml` header is **required** for Versori to automatically parse the XML. Without this header, Versori will not parse the XML and you'll need to manually parse using `XMLParserService`.
|
|
1185
|
+
|
|
1186
|
+
**✅ CORRECT:**
|
|
1187
|
+
```bash
|
|
1188
|
+
curl -X POST https://{workspace}.versori.run/order-update \
|
|
1189
|
+
-H "Content-Type: application/xml" \ # ✅ Required!
|
|
1190
|
+
-H "X-API-Key: your-api-key" \
|
|
1191
|
+
--data-binary @order-update.xml
|
|
1192
|
+
```
|
|
1193
|
+
|
|
1194
|
+
**❌ WRONG:**
|
|
1195
|
+
```bash
|
|
1196
|
+
curl -X POST https://{workspace}.versori.run/order-update \
|
|
1197
|
+
-H "Content-Type: text/plain" \ # ❌ Wrong Content-Type
|
|
1198
|
+
-H "X-API-Key: your-api-key" \
|
|
1199
|
+
--data-binary @order-update.xml
|
|
1200
|
+
```
|
|
1201
|
+
|
|
1202
|
+
---
|
|
1203
|
+
|
|
1204
|
+
## 6. Error Handling
|
|
1205
|
+
|
|
1206
|
+
The template includes comprehensive error handling:
|
|
1207
|
+
|
|
1208
|
+
### Payload Parsing Errors
|
|
1209
|
+
|
|
1210
|
+
- **Empty payload:** Returns error response
|
|
1211
|
+
- **Invalid XML:** Returns error with parse details
|
|
1212
|
+
- **Missing order reference:** Returns error with expected patterns
|
|
1213
|
+
- **Missing attributes:** Returns error if no attributes provided
|
|
1214
|
+
|
|
1215
|
+
### Order Query Errors
|
|
1216
|
+
|
|
1217
|
+
- **Order not found:** Returns error with order reference
|
|
1218
|
+
- **GraphQL query errors:** Returns error with GraphQL error details
|
|
1219
|
+
|
|
1220
|
+
### Mutation Errors
|
|
1221
|
+
|
|
1222
|
+
- **GraphQL mutation errors:** Returns error with mutation error details
|
|
1223
|
+
- **No response data:** Returns error if mutation doesn't return order data
|
|
1224
|
+
|
|
1225
|
+
### Response Format with FC Status Codes
|
|
1226
|
+
|
|
1227
|
+
**Success Response (FC200):**
|
|
1228
|
+
```json
|
|
1229
|
+
{
|
|
1230
|
+
"success": true,
|
|
1231
|
+
"statusCode": "FC200",
|
|
1232
|
+
"orderRef": "ORD-12345",
|
|
1233
|
+
"orderId": "12345",
|
|
1234
|
+
"attributesUpdated": 3,
|
|
1235
|
+
"message": "Order attributes updated successfully"
|
|
1236
|
+
}
|
|
1237
|
+
```
|
|
1238
|
+
|
|
1239
|
+
**Error Responses:**
|
|
1240
|
+
|
|
1241
|
+
**FC400 - Bad Request (Non-retryable):**
|
|
1242
|
+
```json
|
|
1243
|
+
{
|
|
1244
|
+
"success": false,
|
|
1245
|
+
"statusCode": "FC400",
|
|
1246
|
+
"retryable": false,
|
|
1247
|
+
"error": "Bad request. Please verify payload and try again.",
|
|
1248
|
+
"details": "Order reference (orderRef) is required in payload"
|
|
1249
|
+
}
|
|
1250
|
+
```
|
|
1251
|
+
|
|
1252
|
+
**FC409 - Duplicate/Already Exists (Non-retryable):**
|
|
1253
|
+
```json
|
|
1254
|
+
{
|
|
1255
|
+
"success": false,
|
|
1256
|
+
"statusCode": "FC409",
|
|
1257
|
+
"retryable": false,
|
|
1258
|
+
"error": "Entity already exists. Duplicate request detected.",
|
|
1259
|
+
"details": "Order with reference ORD-12345 already exists"
|
|
1260
|
+
}
|
|
1261
|
+
```
|
|
1262
|
+
|
|
1263
|
+
**FC504 - Timeout (Retryable):**
|
|
1264
|
+
```json
|
|
1265
|
+
{
|
|
1266
|
+
"success": false,
|
|
1267
|
+
"statusCode": "FC504",
|
|
1268
|
+
"retryable": true,
|
|
1269
|
+
"error": "Request timeout. Please retry.",
|
|
1270
|
+
"details": "GraphQL mutation timed out after 30 seconds"
|
|
1271
|
+
}
|
|
1272
|
+
```
|
|
1273
|
+
|
|
1274
|
+
---
|
|
1275
|
+
|
|
1276
|
+
## 7. Testing
|
|
1277
|
+
|
|
1278
|
+
### Test with cURL - Flat Structure Format
|
|
1279
|
+
|
|
1280
|
+
**Create `order-update.xml`:**
|
|
1281
|
+
```xml
|
|
1282
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
1283
|
+
<orderUpdate>
|
|
1284
|
+
<orderRef>ORD-12345</orderRef>
|
|
1285
|
+
<externalOrderId>EXT-123456789</externalOrderId>
|
|
1286
|
+
<customerNotes>Please leave package at front door</customerNotes>
|
|
1287
|
+
<priority>high</priority>
|
|
1288
|
+
</orderUpdate>
|
|
1289
|
+
```
|
|
1290
|
+
|
|
1291
|
+
**Send with cURL:**
|
|
1292
|
+
```bash
|
|
1293
|
+
curl -X POST https://{workspace}.versori.run/order-update \
|
|
1294
|
+
-H "Content-Type: application/xml" \
|
|
1295
|
+
-H "X-API-Key: your-api-key" \
|
|
1296
|
+
--data-binary @order-update.xml
|
|
1297
|
+
```
|
|
1298
|
+
|
|
1299
|
+
### Test with Inline XML
|
|
1300
|
+
|
|
1301
|
+
```bash
|
|
1302
|
+
# Test with inline XML (flat format)
|
|
1303
|
+
curl -X POST https://{workspace}.versori.run/order-update \
|
|
1304
|
+
-H "Content-Type: application/xml" \
|
|
1305
|
+
-H "X-API-Key: your-api-key" \
|
|
1306
|
+
-d '<?xml version="1.0" encoding="UTF-8"?><orderUpdate><orderRef>ORD-12345</orderRef><externalOrderId>EXT-123456789</externalOrderId><customerNotes>Please leave package at front door</customerNotes></orderUpdate>'
|
|
1307
|
+
```
|
|
1308
|
+
|
|
1309
|
+
### Complete Sample XML Files
|
|
1310
|
+
|
|
1311
|
+
**Example 1: External System Reference (`external-reference-update.xml`)**
|
|
1312
|
+
```xml
|
|
1313
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
1314
|
+
<orderUpdate>
|
|
1315
|
+
<orderRef>ORD-0017326966182</orderRef>
|
|
1316
|
+
<externalOrderId>EXT-123456789</externalOrderId>
|
|
1317
|
+
<sourceSystem>ERP-SAP</sourceSystem>
|
|
1318
|
+
<integrationId>INT-987654321</integrationId>
|
|
1319
|
+
</orderUpdate>
|
|
1320
|
+
```
|
|
1321
|
+
|
|
1322
|
+
**Example 2: Customer Notes and Business Flags (`customer-notes-update.xml`)**
|
|
1323
|
+
```xml
|
|
1324
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
1325
|
+
<orderUpdate>
|
|
1326
|
+
<orderRef>ORD-0017326966182</orderRef>
|
|
1327
|
+
<customerNotes>Please leave package at front door</customerNotes>
|
|
1328
|
+
<priority>high</priority>
|
|
1329
|
+
<tags>rush,gift</tags>
|
|
1330
|
+
<specialHandling>true</specialHandling>
|
|
1331
|
+
</orderUpdate>
|
|
1332
|
+
```
|
|
1333
|
+
|
|
1334
|
+
### Expected Success Response
|
|
1335
|
+
|
|
1336
|
+
```json
|
|
1337
|
+
{
|
|
1338
|
+
"success": true,
|
|
1339
|
+
"statusCode": "FC200",
|
|
1340
|
+
"orderRef": "ORD-12345",
|
|
1341
|
+
"orderId": "12345",
|
|
1342
|
+
"attributesUpdated": 1,
|
|
1343
|
+
"message": "Order attributes updated successfully"
|
|
1344
|
+
}
|
|
1345
|
+
```
|
|
1346
|
+
|
|
1347
|
+
**FC Status Code:** `FC200` - OK (Successfully - No further action needed)
|
|
1348
|
+
|
|
1349
|
+
### Expected Error Responses
|
|
1350
|
+
|
|
1351
|
+
**FC400 - Bad Request:**
|
|
1352
|
+
```json
|
|
1353
|
+
{
|
|
1354
|
+
"success": false,
|
|
1355
|
+
"statusCode": "FC400",
|
|
1356
|
+
"retryable": false,
|
|
1357
|
+
"error": "Bad request. Please verify payload and try again.",
|
|
1358
|
+
"details": "Order not found: ORD-12345"
|
|
1359
|
+
}
|
|
1360
|
+
```
|
|
1361
|
+
|
|
1362
|
+
**FC409 - Duplicate/Already Exists:**
|
|
1363
|
+
```json
|
|
1364
|
+
{
|
|
1365
|
+
"success": false,
|
|
1366
|
+
"statusCode": "FC409",
|
|
1367
|
+
"retryable": false,
|
|
1368
|
+
"error": "Entity already exists. Duplicate request detected.",
|
|
1369
|
+
"details": "Order with reference ORD-12345 already exists"
|
|
1370
|
+
}
|
|
1371
|
+
```
|
|
1372
|
+
|
|
1373
|
+
**FC504 - Timeout:**
|
|
1374
|
+
```json
|
|
1375
|
+
{
|
|
1376
|
+
"success": false,
|
|
1377
|
+
"statusCode": "FC504",
|
|
1378
|
+
"retryable": true,
|
|
1379
|
+
"error": "Request timeout. Please retry.",
|
|
1380
|
+
"details": "GraphQL mutation timed out"
|
|
1381
|
+
}
|
|
1382
|
+
```
|
|
1383
|
+
|
|
1384
|
+
---
|
|
1385
|
+
|
|
1386
|
+
## 8. Customization
|
|
1387
|
+
|
|
1388
|
+
### Custom Order Reference Element
|
|
1389
|
+
|
|
1390
|
+
If your XML uses different element names, modify `extractOrderRef()`:
|
|
1391
|
+
|
|
1392
|
+
```typescript
|
|
1393
|
+
function extractOrderRef(payload: OrderUpdatePayload): string | null {
|
|
1394
|
+
// Add your custom element extraction logic
|
|
1395
|
+
if (payload.customUpdate && typeof payload.customUpdate === 'object') {
|
|
1396
|
+
const update = payload.customUpdate;
|
|
1397
|
+
if ('$' in update && update.$) {
|
|
1398
|
+
const attrs = update.$ as Record<string, unknown>;
|
|
1399
|
+
if (attrs && typeof attrs.orderId === 'string') {
|
|
1400
|
+
return attrs.orderId;
|
|
1401
|
+
}
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1404
|
+
// ... existing logic
|
|
1405
|
+
}
|
|
1406
|
+
```
|
|
1407
|
+
|
|
1408
|
+
### Custom Attribute Extraction
|
|
1409
|
+
|
|
1410
|
+
To customize how attributes are extracted from XML:
|
|
1411
|
+
|
|
1412
|
+
```typescript
|
|
1413
|
+
function extractAttributes(payload: OrderUpdatePayload): AttributeInput[] {
|
|
1414
|
+
// Add custom extraction logic for your XML structure
|
|
1415
|
+
// e.g., handle nested attribute structures, namespaces, etc.
|
|
1416
|
+
}
|
|
1417
|
+
```
|
|
1418
|
+
|
|
1419
|
+
### Merge with Existing Attributes
|
|
1420
|
+
|
|
1421
|
+
To merge new attributes with existing ones (instead of replacing):
|
|
1422
|
+
|
|
1423
|
+
```typescript
|
|
1424
|
+
// In executeOrderUpdate function, after queryOrderByRef:
|
|
1425
|
+
const existingAttributes = order.attributes || [];
|
|
1426
|
+
const mergedAttributes = [
|
|
1427
|
+
...existingAttributes,
|
|
1428
|
+
...attributes, // New attributes override existing ones with same name
|
|
1429
|
+
];
|
|
1430
|
+
|
|
1431
|
+
const updateVariables = {
|
|
1432
|
+
input: {
|
|
1433
|
+
id: order.id, // ✅ REQUIRED: Use ID from query result
|
|
1434
|
+
attributes: mergedAttributes,
|
|
1435
|
+
},
|
|
1436
|
+
};
|
|
1437
|
+
```
|
|
1438
|
+
|
|
1439
|
+
---
|
|
1440
|
+
|
|
1441
|
+
## Summary
|
|
1442
|
+
|
|
1443
|
+
✅ **DO:**
|
|
1444
|
+
- Use Versori connection for Fluent Commerce credentials
|
|
1445
|
+
- Query order first to validate existence and get ID
|
|
1446
|
+
- Use `id` from query result in UpdateOrderInput (required field)
|
|
1447
|
+
- Handle Versori XML format ($ for attributes, _ for text)
|
|
1448
|
+
- Support multiple XML attribute formats
|
|
1449
|
+
- Log all steps for debugging
|
|
1450
|
+
- Return clear error messages
|
|
1451
|
+
|
|
1452
|
+
❌ **DON'T:**
|
|
1453
|
+
- Hardcode credentials in code
|
|
1454
|
+
- Skip order query validation
|
|
1455
|
+
- Use `ref` in UpdateOrderInput (must use `id: ID!`)
|
|
1456
|
+
- Assume payload format without validation
|
|
1457
|
+
- Skip error handling
|
|
1458
|
+
- Ignore Versori XML parsing format ($ and _)
|
|
1459
|
+
|
|
1460
|
+
---
|
|
1461
|
+
|
|
1462
|
+
## Next Steps
|
|
1463
|
+
|
|
1464
|
+
1. **Deploy:** Copy code to your Versori project
|
|
1465
|
+
2. **Configure:** Set up Fluent Commerce connection and activation variables
|
|
1466
|
+
3. **Test:** Test webhook with sample XML payloads
|
|
1467
|
+
4. **Monitor:** Check Versori logs for order updates
|
|
1468
|
+
|
|
1469
|
+
For more templates, see:
|
|
1470
|
+
- [JSON Payload Template](./template-ingestion-payload-json-order-update-graphql.md)
|
|
1471
|
+
- [Other GraphQL Mutation Templates](./graphql-mutations-guide.md)
|
|
1472
|
+
|