{"id":21043374,"url":"https://github.com/crystallizeapi/node-service-api-request-handlers","last_synced_at":"2025-05-15T17:31:39.796Z","repository":{"id":56800193,"uuid":"479641904","full_name":"CrystallizeAPI/node-service-api-request-handlers","owner":"CrystallizeAPI","description":null,"archived":false,"fork":false,"pushed_at":"2024-01-12T18:39:29.000Z","size":123,"stargazers_count":3,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2024-10-31T17:45:20.530Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://crystallizeapi.github.io/libraries/","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/CrystallizeAPI.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2022-04-09T06:40:30.000Z","updated_at":"2023-06-19T08:23:06.000Z","dependencies_parsed_at":"2023-12-14T01:39:45.778Z","dependency_job_id":"c7d0f67d-fd9c-47e1-8b36-bf6aae198a08","html_url":"https://github.com/CrystallizeAPI/node-service-api-request-handlers","commit_stats":{"total_commits":40,"total_committers":3,"mean_commits":"13.333333333333334","dds":0.125,"last_synced_commit":"6032e887aac87b9745d794dcc5156ea7e2212118"},"previous_names":[],"tags_count":32,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CrystallizeAPI%2Fnode-service-api-request-handlers","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CrystallizeAPI%2Fnode-service-api-request-handlers/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CrystallizeAPI%2Fnode-service-api-request-handlers/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CrystallizeAPI%2Fnode-service-api-request-handlers/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/CrystallizeAPI","download_url":"https://codeload.github.com/CrystallizeAPI/node-service-api-request-handlers/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":225365807,"owners_count":17462973,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-19T14:12:35.744Z","updated_at":"2024-11-19T14:12:36.635Z","avatar_url":"https://github.com/CrystallizeAPI.png","language":"TypeScript","readme":"# Node Service API Request Handlers\n\nThis is a Node library that enables plug and play routing for your Service API when it is using the **Node Service API Router**.\n\nIt provides schemas and handlers that take care of 90% of the work while being highly customizable and totally agnostic of any frameworks.\n\n## Installation\n\nWith NPM:\n\n```bash\nnpm install @crystallize/node-service-api-request-handlers\n```\n\nWith Yarn:\n\n```bash\nyarn add @crystallize/node-service-api-request-handlers\n```\n\n## Agnostic Handlers\n\nThe schemas and handlers can be used with any framework and without Node Service API Router.\n\nA handler signature is always the same:\n\n```typescript\nconst handler = async (payload: Payload, args: Arguments): Promise\u003cSomething\u003e\n```\n\nUsing Node Service API Router, there is a simple integration:\n\n```typescript\nconst bodyConvertedRoutes: ValidatingRequestRouting = {\n    '/endpoint': {\n        post: {\n            schema: Payload,\n            handler: handler,\n            args: (context: Koa.Context): Arguments =\u003e {\n                return {};\n            },\n        },\n    },\n};\n```\n\nOutside of the Node Service API Router:\n\n```typescript\nawait handler(validatePayload\u003cPayload\u003e(body, payloadSchema), {});\n```\n\nNote: As you see, in this context, it’s your responsibility to validate the body with the schema (see a full example below, with Cart Management).\n\n## Cart Management\n\nThe JS API Client already helps you to hydrate products from SKUs or Paths. This handler performs the next step: [it hydrates the products and more](https://github.com/CrystallizeAPI/libraries/blob/main/components/node-service-api-request-handlers/src/cart/handlers.ts#L6).\n\nFirst and as usual, it lets you extend the GraphQL hydration query. Second, it does the price calculation for you.\n\nTo use it:\n\n```typescript\nconst bodyConvertedRoutes: ValidatingRequestRouting = {\n    '/cart': {\n        post: {\n            schema: cartPayload,\n            handler: handleCartRequestPayload,\n            args: (context: Koa.Context): CartHydraterArguments =\u003e {\n                return {\n                    perVariant: () =\u003e {\n                        return {\n                            id: true,\n                        };\n                    },\n                };\n            },\n        },\n    },\n};\n```\n\nThat’s it! The heavy lifting is done for you!\n\nIf you are using the Handler without Node Service API Router, for example with Remix Run:\n\n```typescript\nexport const action: ActionFunction = async ({ request }) =\u003e {\n    const body = await request.json();\n    await handleCartRequestPayload(validatePayload\u003cCartPayload\u003e(body, cartPayload), {\n        currency,\n        perVariant: () =\u003e {\n            return {\n                firstImage: {\n                    url: true,\n                },\n            };\n        },\n});\n```\n\n### Available Arguments\n\n-   currency (required): the Hydrater MUST know the currency to pick a valid PriceVariant\n-   hydraterBySkus (optional): your own Hydrater\n-   extraQuery (optional): if you want more information in the response\n-   perProduct (optional): if you want more information in the response per product hydrated\n-   perVariant (optional): if you want more information in the response per variant hydrated\n-   pricesHaveTaxesIncludedInCrystallize (optional): informs the handler if the prices in Crystallize include taxes or not to adapt the calculations. (default is FALSE)\n-   selectPriceVariant (optional): if you want to pick the PriceVariant (default is the first PriceVariant)\n-   basePriceVariant (optional): if you want to pick the PriceVariant used to calculated strike price (Discount) (default is selectPriceVariant)\n\n## Magick Link Authentication\n\nIt comes with 2 handlers:\n\n-   handleMagickLinkRegisterPayload\n-   handleMagickLinkConfirmationRequestPayload\n\nYou can use them in the following way.\n\nHandling the registration / request for a link:\n\n```typescript\n'/register/email/magicklink': {\n    post: {\n        schema: magickLinkUserInfosPayload,\n        handler: handleMagickLinkRegisterPayload,\n        args: (context: Koa.Context): MagickLinkRegisterArguments =\u003e {\n            return {\n                mailer: createMailer(`${process.env.MAILER_DSN}`),\n                jwtSecret: `${process.env.JWT_SECRET}`,\n                confirmLinkUrl: `http${context.secure ? 's' : ''}://${context.request.host}/confirm/email/magicklink/:token`,\n                subject: \"[Crystallize - Boilerplate] - Magic link login\",\n                from: \"hello@crystallize.com\",\n                buildHtml: (request: MagickLinkUserInfosPayload, link: string) =\u003e mjml2html(\n                    `\u003cmjml\u003e\n                    \u003cmj-body\u003e\n                    \u003cmj-section\u003e\n                        \u003cmj-column\u003e\n                        \u003cmj-text\u003eHi there ${request.email}! Simply follow the link below to login.\u003c/mj-text\u003e\n                        \u003cmj-button href=\"${link}\" align=\"left\"\u003eClick here to login\u003c/mj-button\u003e\n                        \u003c/mj-column\u003e\n                    \u003c/mj-section\u003e\n                    \u003c/mj-body\u003e\n                \u003c/mjml\u003e`\n                ).html,\n                host: context.request.host\n            }\n        }\n    }\n},\n```\n\nAs you can see, the _MagickLinkRegisterArguments_ type lets you inject many things:\n\n-   a `mailer` to send the link as well as all the email information: subject, from, and the HTML\n\n-   the `jwtSecret` to generate and sign the JTW token\n\nthe link to confirm the Magick link: `confirmLinkPath`\n\nYou have control over everything while the handler does the heavy lifting.\n\nThen you can leverage the other handler associated with it:\n\n```typescript\n\"/confirm/email/magicklink/:token\": {\n    get: {\n        schema: null,\n        handler: handleMagickLinkConfirmationRequestPayload,\n        args: (context: Koa.Context): MagickLinkConfirmArguments =\u003e {\n            return {\n                token: context.params.token,\n                host: context.request.host,\n                jwtSecret: `${process.env.JWT_SECRET}`,\n                backLinkPath: 'https://frontend.app.crystal/checkout?token=:token',\n                setCookie: (name: string, value: string) =\u003e {\n                    context.cookies.set(name, value, { httpOnly: false, secure: context.secure });\n                }\n            }\n        }\n    },\n},\n```\n\nOf course, it matches the _confirmLinkPath_ passed in the first handler. It is also interesting to note that there is no Schema because there is no body for those requests.\n\nYou also need to pass:\n\n-   the `jwtSecret` to decode and verify the token\n-   provide a link `backLinkPath` to inform the handler where to redirect the user (most likely to your frontend)\n\nOnce the token is checked and valid, the handler will generate 2 other tokens:\n\n-   a first JWT token that will be saved in the Cookie. This token can then be used to authenticate requests on your service API.\n-   a second JWT token that will be passed to the `backLinkPath`. This token SHOULD NOT be used for authentication, but it is actually a nice format (JWT) to transport non-sensitive information to your frontend.\n\n## Orders\n\nThese 2 handlers are very simple ones that will check that one or more Orders actually match the authenticated user after it has fetched the Order(s):\n\n```typescript\n\"/orders\": {\n    get: {\n        schema: null,\n        authenticated: true,\n        handler: handleOrdersRequestPayload,\n        args: (context: Koa.Context): OrdersArguments =\u003e {\n            return {\n                user: context.user\n            }\n        }\n    }\n},\n\"/order/:id\": {\n    get: {\n        schema: null,\n        authenticated: true,\n        handler: handleOrderRequestPayload,\n        args: (context: Koa.Context): OrderArguments =\u003e {\n            return {\n                user: context.user,\n                orderId: context.params.id\n            };\n        }\n    }\n},\n```\n\nThis is a useful endpoint to display the Order(s) to the customer and enforce that this customer is logged in.\n\n## Stripe Payment\n\nThere are 2 handlers to handle payment with Stripe.\n\nThe first handler is to manage the creation of the Stripe Payment Intent:\n\n```typescript\nconst body = await request.json();\nconst data = await handleStripeCreatePaymentIntentRequestPayload(validatePayload(body, stripePaymentIntentPayload), {\n    secret_key: process.env.STRIPE_SECRET_KEY,\n    fetchCart: async () =\u003e {\n        const cartId = body.cartId as string;\n        const cartWrapper = await cartWrapperRepository.find(cartId);\n        if (!cartWrapper) {\n            throw {\n                message: `Cart '${cartId}' does not exist.`,\n                status: 404,\n            };\n        }\n        return cartWrapper.cart;\n    },\n    createIntentArguments: (cart: Cart) =\u003e {\n        return {\n            amount: cart.total.net * 100,\n            currency: cart.total.currency,\n        };\n    },\n});\n```\n\nArguments are:\n\n-   secret_key (required): to communicate with Stripe\n-   fetchCart (required): provide the hander a way to fetch the Cart\n-   createIntentArguments (required): using the Cart as input, return the parameters to put in the Stripe Intent\n\nThe second handler is to handle the Webhook that Stripe will call to inform about the Payment Intent:\n\n```typescript\nconst body = await request.json();\nconst data = await handleStripePaymentIntentWebhookRequestPayload(body, {\n    secret_key: process.env.STRIPE_SECRET_KEY,\n    endpointSecret: process.env.STRIPE_SECRET_PAYMENT_INTENT_WEBHOOK_ENDPOINT_SECRET,\n    signature: request.headers.get('stripe-signature') as string,\n    rawBody: body,\n    handleEvent: async (eventName: string, event: any) =\u003e {\n        const cartId = event.data.object.metadata.cartId;\n        switch (eventName) {\n            case 'payment_intent.succeeded':\n                const cartWrapper = await cartWrapperRepository.find(cartId);\n                if (!cartWrapper) {\n                    throw {\n                        message: `Cart '${cartId}' does not exist.`,\n                        status: 404,\n                    };\n                }\n            // your own logic\n        }\n    },\n});\n```\n\nArguments are:\n\n-   secret_key (required): to communicate with Stripe\n-   endpointSecret (required): to verify the Signature from Stripe\n-   signature (required): receive in the Request to enforce validation that is coming from Stripe\n-   rawBody (required): needed to validate the Request Signature\n-   handleEvent (required): your custom logic\n\n## QuickPay Payment\n\nThere are 2 handlers to handle payment with QuickPay.\n\nThe first handler is to manage the creation of the Quick Payment and the Link:\n\n```typescript\nconst body = await httpRequest.json();\nconst data = await handleQuickPayCreatePaymentLinkRequestPayload(\n    validatePayload(body, quickPayCreatePaymentLinkPayload),\n    {\n        api_key: process.env.QUICKPAY_API_KEY,\n        fetchCart: async () =\u003e {\n            const cartId = body.cartId as string;\n            const cartWrapper = await cartWrapperRepository.find(cartId);\n            if (!cartWrapper) {\n                throw {\n                    message: `Cart '${cartId}' does not exist.`,\n                    status: 404,\n                };\n            }\n            return cartWrapper.cart;\n        },\n        createPaymentArguments: (cart: Cart) =\u003e {\n            const cartId = body.cartId as string;\n            return {\n                amount: cart.total.net * 100, // in cents\n                currency: cart.total.currency,\n                urls: {\n                    continue: `${baseUrl}/order/cart/${cartId}`,\n                    cancel: `${baseUrl}/order/cart/${cartId}`,\n                    callback: `${baseUrl}/api/webhook/payment/quickpay`,\n                },\n            };\n        },\n    },\n);\n```\n\nArguments are:\n\n-   api_key (required): to communicate with QuickPay\n-   fetchCart (required): provide the hander a way to fetch the Cart\n-   createPaymentArguments (required): using the Cart as input, return the parameters to put in the QuickPay Payment. This is also where you will pass the Return URLs.\n\nThe second handler is to handle the Webhook that QuickPay will call to inform about the Payment:\n\n```typescript\nconst body = await httpRequest.json();\nconst data = await handleQuickPayPaymentUpdateWebhookRequestPayload(body, {\n    private_key: process.env.QUICKPAY_PRIVATE_KEY,\n    signature: httpRequest.headers.get('Quickpay-Checksum-Sha256') as string,\n    rawBody: body,\n    handleEvent: async (event: any) =\u003e {\n        const cartId = event.variables.cartId;\n        switch (event.type?.toLowerCase()) {\n            case 'payment':\n                const cartWrapper = await cartWrapperRepository.find(cartId);\n                if (!cartWrapper) {\n                    throw {\n                        message: `Cart '${cartId}' does not exist.`,\n                        status: 404,\n                    };\n                }\n            // your own logic\n        }\n    },\n});\n```\n\nArguments are:\n\n-   private_key (required): to verify the Signature from QuickPay\n-   signature (required): receive in the Request to enforce validation that is coming from QuickPay\n-   rawBody (required): needed to validate the Request Signature\n-   handleEvent (required): your custom logic\n\n## Montonio Payment\n\nThere are 2 handlers to handle payment with Montonio.\n\nThe first handler is to manage the creation of the Link:\n\n```typescript\nawait handleMontonioCreatePaymentLinkRequestPayload(validatePayload(payload, montonioCreatePaymentLinkPayload), {\n    origin: process.env.MONTONIO_ORIGIN,\n    access_key: process.env.MONTONIO_ACCESS_KEY,\n    secret_key: process.env.MONTONIO_SECRET_KEY,\n    fetchCart: async () =\u003e {\n        return cartWrapper.cart;\n    },\n    createPaymentArguments: (cart: Cart) =\u003e {\n        const orderCartLink = buildLanguageMarketAwareLink(\n            `/order/cart/${cartWrapper.cartId}`,\n            context.language,\n            context.market,\n        );\n        return {\n            amount: cart.total.gross,\n            currency: cart.total.currency,\n            urls: {\n                return: `${context.baseUrl}${orderCartLink}`,\n                notification: `${context.baseUrl}/api/webhook/payment/montonio`,\n            },\n            customer: {\n                email: cartWrapper.customer.email,\n                firstName: cartWrapper.customer.firstName,\n                lastName: cartWrapper.customer.lastName,\n            },\n        };\n    },\n});\n```\n\nArguments are:\n\n-   origin(required): to tell the SDK if that’s toward the sandbox or any other Montonio endpoint.\n-   access_key(required): to identify the call to Montonio\n-   secret_key(required): to sign the JWT\n-   fetchCart (required): provide the hander a way to fetch the Cart\n-   createPaymentArguments (required): using the Cart as input, return the parameters to put in the Montonio Payment. This is also where you will pass the Return URLs.\n\nThe second handler is to handle the Webhook that Montonio will call to inform about the Payment:\n\n```typescript\nawait handleMontonioPaymentUpdateWebhookRequestPayload(\n    {},\n    {\n        secret_key: process.env.MONTONIO_SECRET_KEY,\n        token,\n        handleEvent: async (event: any) =\u003e {\n            const cartId = event.merchant_reference;\n            switch (event.status) {\n                case 'finalized':\n                    const cartWrapper = await cartWrapperRepository.find(cartId);\n                    if (!cartWrapper) {\n                        throw {\n                            message: `Cart '${cartId}' does not exist.`,\n                            status: 404,\n                        };\n                    }\n                // your own logic\n            }\n        },\n    },\n);\n```\n\nArguments are:\n\n-   secret_key (required): to verify the Signature from Montonio\n-   token (required): the token provided by Montonio\n-   handleEvent (required): your custom logic\n\n## Adyen Payment\n\nThere are 2 handlers to handle payment with Adyen.\n\nThe first handler is to manage the creation of the session:\n\n```typescript\nawait handleAdyenPaymentSessionPayload(validatePayload(payload, adyenPaymentPayload), {\n    currency,\n    returnUrl: `${context.baseUrl}${orderCartLink}`,\n    merchantAccount: process.env.ADYEN_MERCHANT_ACCOUNT,\n    apiKey: process.env.ADYEN_API_KEY,\n    env: process.env.ADYEN_ENV,\n    countryCode: currency === 'NOK' ? 'NO' : currency === 'USD' ? 'US' : 'FR',\n    fetchCart: async () =\u003e {\n        return cartWrapper.cart;\n    },\n});\n```\n\nArguments are:\n\n-   currency (required): the currency the payment will take place in\n-   merchantAccount (required): your Adyen merchant account name\n-   apiKey (required): to communicate with Adyen\n-   env (required): the Adyen environment, can either be 'Live' or 'TEST'\n-   countrCode (required): the ISO country code where the transaction is taking place\n-   fetchCart (required): provide the hander a way to fetch the Cart\n\nThe second handler is to handle the Webhook that Adyen will call to inform about the Payment:\n\n```typescript\nawait handleAdyenWebhookRequestPayload(payload, {\n    handleEvent: async () =\u003e {\n        for (let i = 0; i \u003c payload?.notificationItems?.length; i++) {\n            const event = payload?.notificationItems[i]?.NotificationRequestItem;\n            const cartId = event.merchantReference;\n\n            switch (event.eventCode) {\n                case 'AUTHORISATION':\n                    const cartWrapper = await cartWrapperRepository.find(cartId);\n                    if (event.success !== 'true') {\n                        throw {\n                            message: `Payment failed for cart '${cartId}'.`,\n                            status: 403,\n                        };\n                    }\n                    if (!cartWrapper) {\n                        throw {\n                            message: `===\u003e Cart '${cartId}' does not exist.`,\n                            status: 404,\n                        };\n                    }\n                // your custom logic here\n            }\n        }\n    },\n});\n```\n\nArgument is:\n\n-   handleEvent (required): your custom logic\n\n## Razorpay Payment\n\nThere are 2 handlers to handle payment with Razorpay.\n\nThe first handler is to manage the creation of the order:\n\n```typescript\nawait handleRazorPayOrderPayload(validatePayload(payload, razorPayPaymentPayload), {\n    currency: cartWrapper.cart.total.currency.toUpperCase(),\n    credentials: {\n        key_id: process.env.RAZORPAY_ID,\n        key_secret: process.env.RAZORPAY_SECRET,\n    },\n    fetchCart: async () =\u003e {\n        return cartWrapper.cart;\n    },\n});\n```\n\nArguments are:\n\n-   currency (required): the currency the payment will take place in\n-   credentials: includes both the key_id and the key_secret to communicate with Razorpay\n-   fetchCart (required): provide the hander a way to fetch the Cart\n\nThe second handler is to verify the transaction:\n\n```typescript\nawait handleRazorPayPaymentVerificationPayload(payload, {\n    orderCreationId: payload.orderCreationId,\n    razorpayPaymentId: payload.razorpayPaymentId,\n    razorpayOrderId: payload.razorpayOrderId,\n    razorpaySignature: payload.razorpaySignature,\n    key_secret: process.env.RAZORPAY_SECRET,\n    key_id: process.env.RAZORPAY_ID,\n    handleEvent: async (eventName: string, event: any) =\u003e {\n        const cartId = event.notes.cartId;\n        const cartWrapper = await cartWrapperRepository.find(cartId);\n        if (!cartWrapper) {\n            throw {\n                message: `Cart '${cartId}' does not exist.`,\n                status: 404,\n            };\n        }\n        switch (eventName) {\n            case 'success':\n            // your custom logic here\n        }\n    },\n});\n```\n\nArguments are:\n\n-   orderCreationId (required): the order creation ID sent by Razorpay in the previous step\n-   razorpayPaymentId (required): the payment ID receieved in the last step\n-   razorparOrderId (required): different from the order creation ID, recieved when the order is created in Razorpay\n-   razorpaySignature (required): receive in the Request to enforce validation that is coming from Razorpay\n-   key_secret (required): to communicate with Razorpay\n-   key_id (required): API key ID to communicate with Razorpay\n-   handleEvent (required): your custom logic\n\n## Vipps Payment\n\nThis libs provide many handlers to handle payment and login with Vipps and some functions to ease the integration.\n\n\u003e Vipps does not have Webhooks mechanism yet, so polling must be used.\n\nThe first handler is to manage the creation of a Checkout Session:\n\n```typescript\nconst handlingResult = await handleVippsCreateCheckoutSessionRequestPayload(\n    validatePayload(payload, vippsInitiatePaymentPayload),\n    {\n        origin: process.env.VIPPS_ORIGIN,\n        clientId: process.env.VIPPS_CLIENT_ID,\n        clientSecret: process.env.VIPPS_CLIENT_SECRET,\n        merchantSerialNumber: process.env.VIPPS_MSN,\n        subscriptionKey: process.env.VIPPS_SUBSCRIPTION_KEY,\n        fetchCart: async () =\u003e {\n            return cartWrapper.cart;\n        },\n        createCheckoutArguments: (cart: Cart) =\u003e {\n            return {\n                amount: cart.total.gross * 100,\n                currency: 'NOK',\n                callbackUrl,\n                returnUrl,\n                callbackAuthorizationToken,\n                paymentDescription: `Payment for Cart XXX`,\n            };\n        },\n    },\n);\n```\n\nArguments are:\n\n-   all the env variables that act as credentials to discuss with Vipps, this library handles the heavy lifting.\n-   fetchCart (required): provide the hander a way to fetch the Cart\n-   createCheckoutArguments(required): is the way to customize your Vipps Checkout experience\n\nThe second handler is really similar, to handle a flow using the Vipps Payment API, you have more control here.\n\n```typescript\nconst handlingResult = await handleVippsInitiatePaymentRequestPayload(\n    validatePayload(payload, vippsInitiatePaymentPayload),\n    {\n        ...sameAsTheOther,\n        createIntentArguments: (cart: Cart) =\u003e {\n            return {\n                amount: cart.total.gross * 100,\n                currency: 'NOK',\n                paymentMethod: vippsMethod as 'CARD' | 'WALLET',\n                userFlow: vippsFlow as 'PUSH_MESSAGE' | 'NATIVE_REDIRECT' | 'WEB_REDIRECT' | 'QR',\n                returnUrl,\n            };\n        },\n    },\n);\n```\n\nAlmost the same arguments but with the other function:\n\n-   createIntentArguments(required): is the way to customize your Vipps ePayment flow experience.\n\nThe third handler is to verify the transaction:\n\nOnce again Vipps does not have Webhooks so this is just a passthrough that you can call directly for now.\n\n```typescript\nawait handleVippsPayPaymentUpdateWebhookRequestPayload(payload, {\n    handleEvent: async (payment: any) =\u003e {\n        if (payment.state === 'AUTHORIZED') {\n            const orderCreatedConfirmation = await pushOrder(cartWrapperRepository, apiClient, cartWrapper, {...});\n            const credentials: VippsAppCredentials = {...};\n            const receipt: VippsReceipt = {...}\n            };\n            addVippsReceiptOrder({\n                paymentType: \"ecom\",\n                orderId: cartWrapper.cartId,\n                receipt,\n            }, credentials)\n            return orderCreatedConfirmation;\n        }\n    },\n```\n\nArguments are:\n\n-   handleEvent (required): your custom logic\n\nThis is interesting to see the usage of the function `addVippsReceiptOrder` which is an helper to push a `VippsReceipt` with the Order on Vipps side.\n\nThe last bit of the Vipps integration is the Polling mechanism, you can use any system to run that code every 2-5 seconds and this library provides you 2 methods to check the payment status:\n\n```typescript\nfetchVippsCheckoutSession = (url: string, credentials: VippsAppCredentials) =\u003e Promise\u003c{ session: any, payment?: any}\u003e\n\nfetchVippsPayment = (reference: string, credentials: VippsAppCredentials) =\u003e Promise\u003cany|undefined\u003e\n```\n\n`fetchVippsCheckoutSession` is actually using `fetchVippsPayment` when the Session has a payment associated with it. (during the flow).\n\nThe final usage could look similar to this:\n\n```typescript\nconst handlingResult = {.../* See below */}\nif (handlingResult) {\n    pollingUntil(async () =\u003e {\n        if (handlingResult.pollingUrl) {\n            const { payment, session } = await fetchVippsCheckoutSession(handlingResult.pollingUrl, credentials);\n            if (!payment) {\n                return false;\n            }\n            await handleVippsPayPaymentUpdateWebhookRequestPayload(...)\n            return session.paymentDetails?.state !== 'CREATED';\n        }\n\n        const payment = await fetchVippsPayment(handlingResult.reference, credentials);\n        if (!payment) {\n            return false;\n        }\n        await handleVippsPayPaymentUpdateWebhookRequestPayload(...)\n        return payment.state !== 'CREATED';\n    });\n}\n```\n\nThe `pollingUntil` function is a simple function that run `setTimeout`.\nIf the payment state is different than `CREATED` we stop polling.\n\nThe fourth handler is to manage the Express Checkout, the logic is similar\n\n```typescript\nawait handleVippsInitiateExpressCheckoutRequestPayload({ cartId: cartWrapper.cartId },\n    {\n        ...,\n        callbackPrefix: `${context.baseUrl}${webhookCallbackUrl}`,\n        consentRemovalPrefix: `${context.baseUrl}${buildLanguageMarketAwareLink('/', context.language, context.market)}`,\n        fallback: `${context.baseUrl}${orderCartUrl}`,\n        extraMerchantInfo: {...}\n    },\n);\n```\n\nThen you can continue the flow like the previous method.\n\nThe fifth handler is to handle the login and it behaves the same way as the magick link feature described above, but instead of receiving a JWT from an link in an email it receives a `code` and a `state` from Vipps.\n\n```typescript\nawait handleVippsLoginOAuthRequestPayload(\n    {\n        context.url.searchParams.get('code'),\n        state: context.url.searchParams.get('state'),\n    },\n    {\n        ...credentials,\n        host: context.host,\n        expectedState,\n        redirectUri,\n        jwtSecret,\n        backLinkPath: `${frontendUrl}${backLinkPath}?token=:token`,\n        setCookie: (name: string, value: string) =\u003e {\n            {...}\n        },\n        onUserInfos: async (userInfos) =\u003e {\n            {...}\n        },\n    },\n);\n```\n\n\u003e Under the hood, the handler verifies the Code with Vipps and it fetches the UserInfos.\n\u003e Then the handler behave the same as the Magick Link handler, and you have the `onUserInfos` hook that you can use to do more with data coming from Vipps.\n\n[crystallizeobject]: crystallize_marketing|folder|62561a2ab30ff82a1f664932\n\n## Dintero Payment\n\nThere are 2 handlers to handle payment with Dintero.\n\nThe first handler is to manage the creation of the session:\n\n```typescript\nawait handleDinteroPaymentSessionPayload(validatePayload(payload, dinteroPaymentPayload), {\n    credentials: {\n        clientId: process.env.DINTERO_CLIENT_ID,\n        clientSecret: process.env.DINTERO_CLIENT_SECRET,\n        accountId: process.env.DINTERO_ACCOUNT_ID,\n    },\n    returnUrl: `${context.baseUrl}${orderCartLink}`,\n    fetchCart: async () =\u003e {\n        return cartWrapper.cart;\n    },\n});\n```\n\nArguments are:\n\n-   credentials (required): includes the client_id, account_id, and the client_secret to communicate with Dintero\n-   returnUrl (required): the url to redirect to once the payment is done\n-   fetchCart (required): provide the handler a way to fetch the Cart\n\nThe second handler is to verify the transaction:\n\n```typescript\nawait handleDinteroVerificationPayload(payload, {\n    credentials: {\n        clientId: process.env.DINTERO_CLIENT_ID ?? storeFrontConfig.configuration?.DINTERO_CLIENT_ID ?? '',\n        clientSecret: process.env.DINTERO_CLIENT_SECRET ?? storeFrontConfig.configuration?.DINTERO_CLIENT_SECRET ?? '',\n        accountId: process.env.DINTERO_ACCOUNT_ID ?? storeFrontConfig.configuration?.DINTERO_ACCOUNT_ID ?? '',\n    },\n    transactionId: payload,\n    handleEvent: async (eventName: string, event: any) =\u003e {\n        const cartId = event.merchant_reference;\n        const cartWrapper = await cartWrapperRepository.find(cartId);\n        if (!cartWrapper) {\n            throw {\n                message: `Cart '${cartId}' does not exist.`,\n                status: 404,\n            };\n        }\n        switch (eventName) {\n            case 'AUTHORIZED':\n                const orderCreatedConfirmation = await pushOrder(cartWrapperRepository, apiClient, cartWrapper!, {\n                    //@ts-ignore\n                    provider: 'custom',\n                    custom: {\n                        properties: [\n                            { property: 'payment_provider', value: 'dintero' },\n                            { property: 'dintero_transaction_id', value: event.id },\n                        ],\n                    },\n                });\n                return orderCreatedConfirmation;\n        }\n    },\n});\n```\n\nArguments are:\n\n-   credentials (required): includes the client_id, account_id, and the client_secret to communicate with Dintero\n-   transactionId: the transaction ID returned by Dintero once the transaction goes through\n-   handleEvent (required): your custom logic\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcrystallizeapi%2Fnode-service-api-request-handlers","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcrystallizeapi%2Fnode-service-api-request-handlers","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcrystallizeapi%2Fnode-service-api-request-handlers/lists"}