diff --git a/components/fiserv/actions/create-checkout/create-checkout.mjs b/components/fiserv/actions/create-checkout/create-checkout.mjs new file mode 100644 index 0000000000000..c29308e120d46 --- /dev/null +++ b/components/fiserv/actions/create-checkout/create-checkout.mjs @@ -0,0 +1,126 @@ +import app from "../../fiserv.app.mjs"; +import utils from "../../common/utils.mjs"; + +export default { + key: "fiserv-create-checkout", + name: "Create Checkout", + description: "Initiate a payment request by passing all the required parameters. It creates a new payment transaction and returns the redirect URL that includes transaction ID. [See the documentation](https://docs.fiserv.dev/public/reference/postcheckouts).", + version: "0.0.1", + type: "action", + props: { + app, + storeId: { + type: "string", + label: "Store ID", + description: "Store id to be used for processing this payment. It also acts as an identifier for your store to load the checkout pages linked to it. If no checkout pages are found, default payment page will be rendered for that transaction.", + }, + merchantTransactionId: { + type: "string", + label: "Merchant Transaction ID", + description: "You can use this parameter to tag a unique identifier to this transaction for future reference.", + optional: true, + }, + transactionOrigin: { + type: "string", + label: "Transaction Origin", + description: "This parameter is used to flag the transaction source correctly. The possible values are `ECOM` (if the order was recieved from online shop), `MAIL` & `PHONE`.", + options: [ + "ECOM", + "MAIL", + "PHONE", + ], + }, + transactionType: { + type: "string", + label: "Transaction Type", + description: "You can use this parameter to specify the type of transaction you want to perform. Allowed values are: `SALE`, `PRE-AUTH`, `ZERO-AUTH`", + options: [ + "SALE", + "PRE-AUTH", + "ZERO-AUTH", + ], + }, + transactionAmount: { + type: "object", + label: "Transaction Amount", + description: "Object contains `total` transaction amount, `currency`, tax and discount details. Example: `{\"total\":123,\"currency\":\"EUR\",\"components\":{\"subtotal\":115,\"vat\":3,\"shipping\":2.5}}`", + optional: true, + }, + order: { + type: "string", + label: "Order", + description: "Object contains order related details. [See the documentation](https://docs.fiserv.dev/public/reference/postcheckouts).", + optional: true, + }, + checkoutSettings: { + type: "string", + label: "Checkout Settings", + description: "Object contains checkout related settings. [See the documentation](https://docs.fiserv.dev/public/reference/postcheckouts).", + default: JSON.stringify({ + locale: "en_US", + }), + }, + paymentMethodDetails: { + type: "string", + label: "Payment Method Details", + description: "Object contains payment method related details. [See the documentation](https://docs.fiserv.dev/public/reference/postcheckouts).", + default: JSON.stringify({ + cards: { + authenticationPreferences: { + challengeIndicator: "01", + skipTra: false, + }, + createToken: { + declineDuplicateToken: false, + reusable: true, + toBeUsedFor: "UNSCHEDULED", + }, + tokenBasedTransaction: { + transactionSequence: "FIRST", + }, + }, + sepaDirectDebit: { + transactionSequenceType: "SINGLE", + }, + }), + }, + }, + methods: { + createCheckout(args = {}) { + return this.app.post({ + path: "/checkouts", + ...args, + }); + }, + }, + async run({ $ }) { + const { + createCheckout, + storeId, + merchantTransactionId, + transactionOrigin, + transactionType, + transactionAmount, + order, + checkoutSettings, + paymentMethodDetails, + } = this; + + const response = await createCheckout({ + $, + data: { + storeId, + merchantTransactionId, + transactionOrigin, + transactionType, + transactionAmount: utils.parseJson(transactionAmount), + order: utils.parseJson(order), + checkoutSettings: utils.parseJson(checkoutSettings), + paymentMethodDetails: utils.parseJson(paymentMethodDetails), + }, + }); + + $.export("$summary", `Successfully created checkout. Redirect URL: ${response.redirectionUrl}`); + return response; + }, +}; diff --git a/components/fiserv/actions/retrieve-checkout-details/retrieve-checkout-details.mjs b/components/fiserv/actions/retrieve-checkout-details/retrieve-checkout-details.mjs new file mode 100644 index 0000000000000..b87822a944b1c --- /dev/null +++ b/components/fiserv/actions/retrieve-checkout-details/retrieve-checkout-details.mjs @@ -0,0 +1,42 @@ +import app from "../../fiserv.app.mjs"; + +export default { + key: "fiserv-retrieve-checkout-details", + name: "Retrieve Checkout Details", + description: "Retrieve details about a specific checkout using the identifier returned when it was created. [See the documentation](https://docs.fiserv.dev/public/reference/get-checkouts-id).", + version: "0.0.1", + type: "action", + props: { + app, + checkoutId: { + type: "string", + label: "Checkout ID", + description: "The unique identifier for the checkout.", + optional: false, + }, + }, + methods: { + getCheckoutDetails({ + checkoutId, ...args + } = {}) { + return this.app._makeRequest({ + path: `/checkouts/${checkoutId}`, + ...args, + }); + }, + }, + async run({ $ }) { + const { + getCheckoutDetails, + checkoutId, + } = this; + + const response = await getCheckoutDetails({ + $, + checkoutId, + }); + + $.export("$summary", `Successfully retrieved details for checkout ID ${this.checkoutId}`); + return response; + }, +}; diff --git a/components/fiserv/common/constants.mjs b/components/fiserv/common/constants.mjs new file mode 100644 index 0000000000000..5a21491cd5823 --- /dev/null +++ b/components/fiserv/common/constants.mjs @@ -0,0 +1,18 @@ +const ENVIRONMENT = { + SANDBOX: "sandbox", + PRODUCTION: "production", +}; + +const SANDBOX_PATH = "/sandbox"; + +const API_PATH = { + DEFAULT: "/exp/v1", + PAYMENTS: "/ipp/payments-gateway/v2", + FRAUD: "/ipp/fraud/v1", +}; + +export default { + ENVIRONMENT, + SANDBOX_PATH, + API_PATH, +}; diff --git a/components/fiserv/common/utils.mjs b/components/fiserv/common/utils.mjs new file mode 100644 index 0000000000000..6b39799b86348 --- /dev/null +++ b/components/fiserv/common/utils.mjs @@ -0,0 +1,26 @@ +const parseJson = (input) => { + const parse = (value) => { + if (typeof(value) === "string") { + try { + return parseJson(JSON.parse(value)); + } catch (e) { + return value; + } + } else if (typeof(value) === "object" && value !== null) { + return Object.entries(value) + .reduce((acc, [ + key, + val, + ]) => Object.assign(acc, { + [key]: parse(val), + }), {}); + } + return value; + }; + + return parse(input); +}; + +export default { + parseJson, +}; diff --git a/components/fiserv/fiserv.app.mjs b/components/fiserv/fiserv.app.mjs index d966990507988..cabc0cce19d61 100644 --- a/components/fiserv/fiserv.app.mjs +++ b/components/fiserv/fiserv.app.mjs @@ -1,11 +1,93 @@ +import { v4 as uuid } from "uuid"; +import crypto from "crypto"; +import { axios } from "@pipedream/platform"; +import constants from "./common/constants.mjs"; + export default { type: "app", app: "fiserv", - propDefinitions: {}, methods: { - // this.$auth contains connected account data - authKeys() { - console.log(Object.keys(this.$auth)); + getAuth() { + const { + url, + api_key: apiKey, + secret_key: secretKey, + environment = constants.ENVIRONMENT.SANDBOX, + } = this.$auth; + return { + url, + apiKey, + secretKey, + environment, + }; + }, + getUrl(path, apiPath = constants.API_PATH.DEFAULT) { + const { + url, + environment, + } = this.getAuth(); + const baseUrl = environment === constants.ENVIRONMENT.SANDBOX + ? `${url}${constants.SANDBOX_PATH}${apiPath}` + : `${url}${apiPath}`; + return `${baseUrl}${path}`; + }, + /** + * Example at https://docs.fiserv.dev/public/docs/message-signature#example-of-code + */ + getSignatureHeaders(data) { + const { + apiKey, + secretKey, + environment, + } = this.getAuth(); + + if (environment === constants.ENVIRONMENT.SANDBOX) { + return; + } + + const clientRequestId = uuid(); + const timestamp = Date.now().toString(); + const requestBody = JSON.stringify(data) || ""; + const rawSignature = apiKey + clientRequestId + timestamp + requestBody; + + const computedHmac = + crypto.createHmac("sha256", secretKey) + .update(rawSignature) + .digest("base64"); + + return { + "Client-Request-Id": clientRequestId, + "Message-Signature": computedHmac, + "Timestamp": timestamp, + }; + }, + getHeaders(headers) { + return { + ...headers, + "Content-Type": "application/json", + "Accept": "application/json", + "API-Key": this.$auth.api_key, + }; + }, + _makeRequest({ + $ = this, path, headers, data, apiPath, ...args + } = {}) { + return axios($, { + ...args, + debug: true, + url: this.getUrl(path, apiPath), + data, + headers: { + ...this.getHeaders(headers), + ...this.getSignatureHeaders(data), + }, + }); + }, + post(args = {}) { + return this._makeRequest({ + method: "POST", + ...args, + }); }, }, -}; \ No newline at end of file +}; diff --git a/components/fiserv/package.json b/components/fiserv/package.json index 818f823ed6bad..46d2b9a1258f8 100644 --- a/components/fiserv/package.json +++ b/components/fiserv/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/fiserv", - "version": "0.0.1", + "version": "0.1.0", "description": "Pipedream Fiserv Components", "main": "fiserv.app.mjs", "keywords": [ @@ -11,5 +11,10 @@ "author": "Pipedream (https://pipedream.com/)", "publishConfig": { "access": "public" + }, + "dependencies": { + "@pipedream/platform": "3.0.3", + "crypto": "^1.0.1", + "uuid": "^11.0.3" } -} \ No newline at end of file +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b9232c120c112..8f6b1e2d9349b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3550,7 +3550,14 @@ importers: specifiers: {} components/fiserv: - specifiers: {} + specifiers: + '@pipedream/platform': 3.0.3 + crypto: ^1.0.1 + uuid: ^11.0.3 + dependencies: + '@pipedream/platform': 3.0.3 + crypto: 1.0.1 + uuid: 11.0.3 components/fivetran: specifiers: {} @@ -13447,55 +13454,6 @@ packages: - aws-crt dev: false - /@aws-sdk/client-sso-oidc/3.600.0_tdq3komn4zwyd65w7klbptsu34: - resolution: {integrity: sha512-7+I8RWURGfzvChyNQSyj5/tKrqRbzRl7H+BnTOf/4Vsw1nFOi5ROhlhD4X/Y0QCTacxnaoNcIrqnY7uGGvVRzw==} - engines: {node: '>=16.0.0'} - dependencies: - '@aws-crypto/sha256-browser': 5.2.0 - '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/client-sts': 3.600.0 - '@aws-sdk/core': 3.598.0 - '@aws-sdk/credential-provider-node': 3.600.0_f7n47caigsrjd2lr2szmwfuee4 - '@aws-sdk/middleware-host-header': 3.598.0 - '@aws-sdk/middleware-logger': 3.598.0 - '@aws-sdk/middleware-recursion-detection': 3.598.0 - '@aws-sdk/middleware-user-agent': 3.598.0 - '@aws-sdk/region-config-resolver': 3.598.0 - '@aws-sdk/types': 3.598.0 - '@aws-sdk/util-endpoints': 3.598.0 - '@aws-sdk/util-user-agent-browser': 3.598.0 - '@aws-sdk/util-user-agent-node': 3.598.0 - '@smithy/config-resolver': 3.0.3 - '@smithy/core': 2.2.3 - '@smithy/fetch-http-handler': 3.2.1 - '@smithy/hash-node': 3.0.2 - '@smithy/invalid-dependency': 3.0.2 - '@smithy/middleware-content-length': 3.0.2 - '@smithy/middleware-endpoint': 3.0.4 - '@smithy/middleware-retry': 3.0.6 - '@smithy/middleware-serde': 3.0.3 - '@smithy/middleware-stack': 3.0.3 - '@smithy/node-config-provider': 3.1.3 - '@smithy/node-http-handler': 3.1.2 - '@smithy/protocol-http': 4.0.3 - '@smithy/smithy-client': 3.1.6 - '@smithy/types': 3.3.0 - '@smithy/url-parser': 3.0.3 - '@smithy/util-base64': 3.0.0 - '@smithy/util-body-length-browser': 3.0.0 - '@smithy/util-body-length-node': 3.0.0 - '@smithy/util-defaults-mode-browser': 3.0.6 - '@smithy/util-defaults-mode-node': 3.0.6 - '@smithy/util-endpoints': 2.0.3 - '@smithy/util-middleware': 3.0.3 - '@smithy/util-retry': 3.0.2 - '@smithy/util-utf8': 3.0.0 - tslib: 2.6.3 - transitivePeerDependencies: - - '@aws-sdk/client-sts' - - aws-crt - dev: false - /@aws-sdk/client-sso/3.423.0: resolution: {integrity: sha512-znIufHkwhCIePgaYciIs3x/+BpzR57CZzbCKHR9+oOvGyufEPPpUT5bFLvbwTgfiVkTjuk6sG/ES3U5Bc+xtrA==} engines: {node: '>=14.0.0'} @@ -13731,7 +13689,7 @@ packages: dependencies: '@aws-crypto/sha256-browser': 5.2.0 '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/client-sso-oidc': 3.600.0_tdq3komn4zwyd65w7klbptsu34 + '@aws-sdk/client-sso-oidc': 3.600.0 '@aws-sdk/core': 3.598.0 '@aws-sdk/credential-provider-node': 3.600.0_f7n47caigsrjd2lr2szmwfuee4 '@aws-sdk/middleware-host-header': 3.598.0 @@ -13773,6 +13731,55 @@ packages: - aws-crt dev: false + /@aws-sdk/client-sts/3.600.0_dseaa2p5u2yk67qiepewcq3hkq: + resolution: {integrity: sha512-KQG97B7LvTtTiGmjlrG1LRAY8wUvCQzrmZVV5bjrJ/1oXAU7DITYwVbSJeX9NWg6hDuSk0VE3MFwIXS2SvfLIA==} + engines: {node: '>=16.0.0'} + dependencies: + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/client-sso-oidc': 3.600.0 + '@aws-sdk/core': 3.598.0 + '@aws-sdk/credential-provider-node': 3.600.0_f7n47caigsrjd2lr2szmwfuee4 + '@aws-sdk/middleware-host-header': 3.598.0 + '@aws-sdk/middleware-logger': 3.598.0 + '@aws-sdk/middleware-recursion-detection': 3.598.0 + '@aws-sdk/middleware-user-agent': 3.598.0 + '@aws-sdk/region-config-resolver': 3.598.0 + '@aws-sdk/types': 3.598.0 + '@aws-sdk/util-endpoints': 3.598.0 + '@aws-sdk/util-user-agent-browser': 3.598.0 + '@aws-sdk/util-user-agent-node': 3.598.0 + '@smithy/config-resolver': 3.0.3 + '@smithy/core': 2.2.3 + '@smithy/fetch-http-handler': 3.2.1 + '@smithy/hash-node': 3.0.2 + '@smithy/invalid-dependency': 3.0.2 + '@smithy/middleware-content-length': 3.0.2 + '@smithy/middleware-endpoint': 3.0.4 + '@smithy/middleware-retry': 3.0.6 + '@smithy/middleware-serde': 3.0.3 + '@smithy/middleware-stack': 3.0.3 + '@smithy/node-config-provider': 3.1.3 + '@smithy/node-http-handler': 3.1.2 + '@smithy/protocol-http': 4.0.3 + '@smithy/smithy-client': 3.1.6 + '@smithy/types': 3.3.0 + '@smithy/url-parser': 3.0.3 + '@smithy/util-base64': 3.0.0 + '@smithy/util-body-length-browser': 3.0.0 + '@smithy/util-body-length-node': 3.0.0 + '@smithy/util-defaults-mode-browser': 3.0.6 + '@smithy/util-defaults-mode-node': 3.0.6 + '@smithy/util-endpoints': 2.0.3 + '@smithy/util-middleware': 3.0.3 + '@smithy/util-retry': 3.0.2 + '@smithy/util-utf8': 3.0.0 + tslib: 2.6.3 + transitivePeerDependencies: + - '@aws-sdk/client-sso-oidc' + - aws-crt + dev: false + /@aws-sdk/core/3.556.0: resolution: {integrity: sha512-vJaSaHw2kPQlo11j/Rzuz0gk1tEaKdz+2ser0f0qZ5vwFlANjt08m/frU17ctnVKC1s58bxpctO/1P894fHLrA==} engines: {node: '>=14.0.0'} @@ -16488,8 +16495,8 @@ packages: resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} dev: true - /@bufbuild/protobuf/2.2.2: - resolution: {integrity: sha512-UNtPCbrwrenpmrXuRwn9jYpPoweNXj8X5sMvYgsqYyaH8jQ6LfUJSk3dJLnBK+6sfYPrF4iAIo5sd5HQ+tg75A==} + /@bufbuild/protobuf/1.10.0: + resolution: {integrity: sha512-QDdVFLoN93Zjg36NoQPZfsVH9tZew7wKDKyV5qRdj8ntT4wQCOradQjRaTdwMhWUYsgKsvCINKKm87FdEk96Ag==} dev: false /@bufbuild/protobuf/2.2.2: @@ -18155,7 +18162,7 @@ packages: '@aws-sdk/client-sns': 3.423.0 '@aws-sdk/client-sqs': 3.423.0 '@aws-sdk/client-ssm': 3.423.0 - '@aws-sdk/client-sts': 3.600.0 + '@aws-sdk/client-sts': 3.600.0_dseaa2p5u2yk67qiepewcq3hkq '@aws-sdk/s3-request-presigner': 3.609.0 '@pipedream/helper_functions': 0.3.12 '@pipedream/platform': 1.6.6 @@ -36785,6 +36792,11 @@ packages: hasBin: true dev: false + /uuid/11.0.3: + resolution: {integrity: sha512-d0z310fCWv5dJwnX1Y/MncBAqGMKEzlBb1AOf7z9K8ALnd0utBX/msg/fA0+sbyN1ihbMsLhrBlnl1ak7Wa0rg==} + hasBin: true + dev: false + /uuid/3.3.2: resolution: {integrity: sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==} deprecated: Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.