bitsnap-checkout
Version:
This is Bitsnap Checkout React library for easy integration with any website which is using React framework
1 lines • 157 kB
Source Map (JSON)
{"version":3,"sources":["../src/components/checkout/ApplePay.tsx","../src/public.api.client.ts","../src/components/checkout/CartProvider.tsx","../src/components/checkout/constants.ts","../src/components/checkout/helper.methods.ts","../src/components/checkout/lib/err.ts","../src/components/checkout/lib/round.number.ts","../src/components/checkout/state.ts","../src/components/checkout/methods.ts","../src/components/checkout/google.pay.mapper.ts","../src/components/checkout/GooglePay.tsx","../src/components/checkout/BitsnapCart.tsx","../src/components/checkout/CartComponent.tsx","../src/components/checkout/CartComponentContent.tsx","../src/components/checkout/CountrySelector.tsx","../src/components/checkout/SingleProduct.tsx","../src/components/checkout/Skeleton.tsx","../src/components/webhook.handler.ts","../src/gen/proto/jobs/v1/integration_event_job_pb.ts","../src/gen/proto/common/v1/environment_pb.ts","../src/gen/proto/integrations/v1/event_data_pb.ts","../src/gen/proto/integrations/v1/order_pb.ts"],"sourcesContent":["import ApplePayButton from \"apple-pay-button\";\nimport { PublicApiClient } from \"src/public.api.client\";\nimport CartProvider, { getProjectID, useCartProvider } from \"./CartProvider\";\nimport { HOST } from \"./constants\";\nimport { isErr } from \"./lib/err\";\nimport { round } from \"./lib/round.number\";\nimport { useQuery } from \"react-query\";\n\ntype AppleButtonType = 'plain' | 'add-money' | 'book' | 'buy' | 'check-out' | 'continue' | 'contribute' | 'donate' | 'order' | 'pay' | 'reload' | 'rent' | 'set-up' | 'subscribe' | 'support' | 'tip' | 'top-up';\ntype ColorType = 'black' | 'white' | 'white-outline';\n\ntype Props = {\n style?: {\n width?: string;\n height?: string;\n borderRadius?: string;\n padding?: string;\n boxSizing?: string;\n };\n buttonType?: AppleButtonType;\n colorType?: ColorType;\n items: { name: string; id: string; price: number; quantity: number; isDeliverable?: boolean; metadata?: { [key: string]: string | undefined } }[];\n onClick?: () => Promise<void>;\n};\n\nfunction ApplePayButtonComponent({ style, buttonType, colorType, items, onClick }: Props) {\n const {\n checkIfApplePayIsAvailable,\n getApplePayPaymentRequest,\n completeApplePayPayment,\n setCountry,\n setCouponCodeIfPossible,\n setDeliveryMethod,\n setEmail,\n setPostalCode,\n clearCart,\n } = useCartProvider();\n const { data: isApplePayAvailable } = useQuery(\"is-apple-pay-available\", checkIfApplePayIsAvailable);\n if (isApplePayAvailable != true) {\n return null;\n }\n\n const expectedItems = items.map(el => ({\n productID: el.id,\n quantity: el.quantity,\n metadata: el.metadata,\n }));\n\n async function beginSession() {\n const requiresShipping = items.find(el => el.isDeliverable === true) != null;\n const session = new ApplePaySession(14, {\n countryCode: \"PL\",\n merchantCapabilities: [\"supports3DS\"],\n supportedNetworks: [\"visa\", \"masterCard\"],\n currencyCode: \"PLN\",\n total: {\n amount: `${round(items.reduce((acc, item) => acc + item.price * item.quantity, 0) / 100, 2)}`,\n label: \"Płatność za koszyk\",\n type: \"pending\",\n },\n lineItems: [\n ...items.map((item) => ({\n amount: `${round((item.price * item.quantity) / 100, 2)}`,\n label: item.name,\n type: \"final\",\n } as ApplePayJS.ApplePayLineItem)),\n requiresShipping ? {\n amount: `16.99`,\n type: \"pending\",\n label: \"Dostawa\",\n } as ApplePayJS.ApplePayLineItem : undefined\n ].filter(el => el != null),\n ...(\n requiresShipping ? {\n shippingMethods: [],\n supportsCouponCode: true,\n requiredBillingContactFields: [\"name\", \"email\", \"postalAddress\", \"phone\"],\n requiredShippingContactFields: [\"name\", \"email\", \"postalAddress\", \"phone\"],\n } : {}\n ),\n });\n await onClick?.();\n const apRequest = await getApplePayPaymentRequest({ expectedItems });\n if (isErr(apRequest)) {\n console.error(\"Error creating ApplePaySession:\", apRequest.error);\n return;\n }\n\n session.oncancel = (event) => {\n console.log(\"ApplePaySession cancelled\", event);\n };\n\n session.onvalidatemerchant = async (event) => {\n try {\n const result = await PublicApiClient.get(HOST).applePayValidateMerchant(\n {\n validationUrl: event.validationURL,\n projectId: getProjectID(),\n },\n );\n console.log(\"merchantSession\", result.merchantSession);\n if (result.merchantSession.length > 0) {\n console.log('completing merchant session');\n session.completeMerchantValidation(\n JSON.parse(result.merchantSession),\n );\n } else {\n session.abort();\n }\n } catch (error) {\n session.abort;\n }\n };\n\n session.onshippingmethodselected = async (event) => {\n console.log(\"shipping method selected\", event);\n await setDeliveryMethod(event.shippingMethod.identifier);\n const apRequest = await getApplePayPaymentRequest({ expectedItems });\n console.log(\"apRequest\", apRequest);\n if (isErr(apRequest)) {\n session.abort();\n throw new Error(\"Failed to get Apple Pay payment request\");\n }\n\n session.completeShippingMethodSelection({\n newTotal: apRequest.total,\n newLineItems: apRequest.lineItems,\n newShippingMethods: apRequest.shippingMethods,\n });\n };\n session.onshippingcontactselected = async (event) => {\n try {\n if (event.shippingContact.countryCode != null) {\n await setCountry(event.shippingContact.countryCode);\n }\n if (event.shippingContact.postalCode != null) {\n await setPostalCode(event.shippingContact.postalCode);\n }\n if (event.shippingContact.phoneNumber != null) {\n await setEmail(event.shippingContact.emailAddress);\n }\n const apRequest = await getApplePayPaymentRequest({ expectedItems });\n if (isErr(apRequest)) {\n session.abort();\n throw new Error(\"Failed to get Apple Pay payment request\");\n }\n\n console.log('apRequest', apRequest);\n session.completeShippingContactSelection({\n newLineItems: apRequest.lineItems,\n newTotal: apRequest.total,\n newShippingMethods: apRequest.shippingMethods,\n });\n } catch (error) {\n console.log(\"shipping contact selected error\", error);\n session.abort();\n }\n };\n\n session.oncouponcodechanged = async (event) => {\n try {\n console.log(\"coupon code changed\", event);\n const result = await setCouponCodeIfPossible(event.couponCode);\n const apRequest = await getApplePayPaymentRequest({ expectedItems });\n console.log(\"apRequest\", apRequest);\n if (isErr(apRequest)) {\n session.abort();\n throw new Error(\"Failed to get Apple Pay payment request\");\n }\n\n session.completeCouponCodeChange({\n errors: isErr(result)\n ? [new ApplePayError(\"couponCodeInvalid\")]\n : undefined,\n newTotal: apRequest.total,\n newLineItems: apRequest.lineItems,\n newShippingMethods: apRequest.shippingMethods,\n });\n } catch (error) {\n console.log(\"coupon code changed error\", error);\n session.abort();\n }\n };\n\n session.onpaymentauthorized = async (event) => {\n try {\n const result = await completeApplePayPayment({\n token: event.payment.token,\n billingContact: event.payment.billingContact,\n shippingContact: event.payment.shippingContact,\n });\n if (isErr(result)) {\n console.log(\"apple pay error\", result);\n session.completePayment({\n status: ApplePaySession.STATUS_FAILURE,\n });\n return;\n }\n\n if (result.redirectURL) {\n setTimeout(() => {\n open(result.redirectURL, \"_self\");\n }, 2000);\n }\n session.completePayment({\n status: result.isSuccess ? ApplePaySession.STATUS_SUCCESS : ApplePaySession.STATUS_FAILURE,\n });\n\n result.isSuccess && clearCart();\n } catch (e) {\n session.completePayment({\n status: ApplePaySession.STATUS_FAILURE,\n });\n }\n // event.complete(ApplePaySession.STATUS_SUCCESS);\n };\n\n session.begin();\n }\n\n return <ApplePayButton style={style} buttonStyle={colorType} type={buttonType} onClick={beginSession} />;\n}\n\nfunction Wrapper(props: Props) {\n return (\n <>\n <CartProvider>\n <ApplePayButtonComponent {...props} />\n </CartProvider>\n </>\n );\n}\nexport default Wrapper;\n","import type { Transport } from \"@connectrpc/connect\";\nimport { createClient, type Client } from \"@connectrpc/connect\";\nimport { createConnectTransport } from \"@connectrpc/connect-web\";\nimport { PublicApiService } from \"./gen/proto/public/v1/public_api_pb\";\n\nexport namespace PublicApiClient {\n export function get(host: string): Client<typeof PublicApiService> {\n return createClient(PublicApiService, getTransport(host));\n }\n\n let transport: Transport | undefined;\n function getTransport(host: string): Transport {\n if (transport == null) {\n transport = createConnectTransport({\n useBinaryFormat: true,\n baseUrl: host + \"/api/rpc\",\n });\n }\n return transport;\n }\n}\n","import { create } from \"@bufbuild/protobuf\";\nimport { createContext, useContext } from \"react\";\nimport { QueryClient, QueryClientProvider } from \"react-query\";\nimport {\n AddressSchema,\n BillingAddressSchema,\n} from \"src/gen/proto/common/v1/address_pb\";\nimport {\n GetPreOrderDetailsRequestSchema,\n OneClickAuthorizePaymentRequest_Gateway,\n PreOrderItemSchema,\n} from \"src/gen/proto/public/v1/public_api_pb\";\nimport { PublicApiClient } from \"src/public.api.client\";\nimport zod from \"zod\";\nimport { HOST, setCustomHost } from \"./constants\";\nimport { buildURL } from \"./helper.methods\";\nimport { Err, isErr } from \"./lib/err\";\nimport { formatCurrency, round } from \"./lib/round.number\";\nimport { LinkRequest } from \"./link.request.schema\";\nimport { createPaymentURL, getReferenceIfPossible, injectReferenceToRequestIfNeeded } from \"./methods\";\nimport { SingleProduct } from \"./product.details.model\";\nimport { mapGooglePayConfiguration } from \"./google.pay.mapper\";\n\nexport const MARKETING_AGREEMENT_ID = \"__m_a\";\n\ntype CartProduct = {\n productID: string;\n quantity: number;\n metadata?: { [key: string]: string | undefined };\n}\n\nexport interface CartMethods {\n checkIfApplePayIsAvailable(): Promise<boolean>;\n getGooglePayConfiguration(args: { items: CartProduct[] }): Promise<{\n isAvailable: true;\n config: google.payments.api.PaymentDataRequest;\n } | {\n isAvailable: false;\n }>;\n\n addProduct(args: CartProduct): Promise<Err | void>;\n\n getProducts: () => Promise<\n | Err\n | {\n id: string;\n productID: string;\n quantity: number;\n metadata?: { [key: string]: string | undefined };\n details?: SingleProduct;\n }[]\n >;\n\n updateQuantity(args: { id: string; quantity: number }): Promise<Err | void>;\n\n removeProductFromCart: (args: { id: string }) => Promise<Err | void>;\n\n clearCart: () => Promise<Err | void>;\n\n getNumberOfElementsInCart: () => Promise<number>;\n\n setCouponCodeIfPossible: (couponCode?: string) => Promise<Err | void>;\n setDeliveryMethod: (deliveryMethod?: string) => Promise<Err | void>;\n setPostalCode: (postalCode?: string) => Promise<Err | void>;\n setEmail: (email?: string) => Promise<Err | void>;\n setCountry: (country: string) => Promise<Err | void>;\n getCountry: () => Promise<Err | string>;\n getAvailableCountries: () => Promise<Err | { name: string; code: string }[]>;\n\n redirectToNextStep: () => Promise<Err | { url: string }>;\n\n getApplePayPaymentRequest: (args?: { expectedItems?: CartProduct[]; }) => Promise<\n Err | ApplePayJS.ApplePayPaymentRequest\n >;\n completeApplePayPayment: (args: {\n token: ApplePayJS.ApplePayPaymentToken;\n shippingContact?: ApplePayJS.ApplePayPaymentContact;\n billingContact?: ApplePayJS.ApplePayPaymentContact;\n }) => Promise<\n | Err\n | {\n isSuccess: boolean;\n redirectURL?: string;\n }\n >;\n completeGooglePayPayment: (args: {\n paymentData: google.payments.api.PaymentData\n }) => Promise<Err | {\n isSuccess: boolean;\n redirectURL?: string;\n }>;\n\n justRedirectToPayment: (args: {\n email?: string;\n productID: string;\n name?: string;\n country?: string;\n marketingAgreement?: boolean;\n }) => Promise<\n | Err\n | {\n url: string;\n }\n >;\n}\n\nconst CartProviderContext = createContext<CartMethods | undefined>(undefined);\n\nexport var bitsnapProjectID: string | undefined = undefined;\nexport function setProjectID(projectID: string) {\n bitsnapProjectID = projectID;\n}\n\nexport function getProjectID(): string | undefined {\n if (bitsnapProjectID != null) {\n return bitsnapProjectID;\n }\n const me = document.querySelector(\n 'script[data-id][data-name=\"internal-cart\"]',\n );\n const projectID = me?.getAttribute(\"data-id\");\n return projectID ?? undefined;\n}\n\nfunction getNewHostIfExist(): string | undefined {\n const me = document.querySelector(\n 'script[data-id][data-name=\"internal-cart\"]',\n );\n const customHost = me?.getAttribute(\"data-custom-host\");\n return customHost ?? undefined;\n}\n\nconst CartProvider = ({ children }: { children: React.ReactNode }) => {\n const queryClient = new QueryClient();\n\n const projectID = getProjectID();\n if (projectID == null) {\n return <></>;\n }\n\n const checkoutMethods = getCheckoutMethods(projectID);\n\n return (\n <QueryClientProvider client={queryClient}>\n <CartProviderContext.Provider value={checkoutMethods}>\n {children}\n </CartProviderContext.Provider>\n </QueryClientProvider>\n );\n};\n\n// @ts-ignore\nconst getProducts: (projectID: string) => Promise<\n | Err\n | {\n id: string;\n productID: string;\n quantity: number;\n metadata?: { [key: string]: string | undefined };\n details?: SingleProduct;\n }[]\n> = async (projectID: string) => {\n const products = getCheckout()?.products ?? [];\n\n const productIds = Array.from(\n new Set(products.map((product) => product.productID)),\n );\n\n const params = new URLSearchParams();\n params.set(\"ids\", productIds.join(\",\"));\n\n const result = await fetch(\n buildURL(projectID, `/products?${params.toString()}`),\n {\n method: \"GET\",\n },\n );\n\n if (result.status != 200) {\n return [];\n }\n\n const payload: {\n success: boolean;\n message?: string | undefined;\n result?: SingleProduct[] | undefined;\n } = await result.json();\n\n products.forEach((product) => {\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-expect-error\n product[\"details\"] = payload.result?.find((el) => {\n if (el.id === product.productID) {\n return true;\n }\n if (el.variants != null && el.variants.length > 0) {\n const index = el.variants.findIndex(\n (variant) => variant.id === product.productID,\n );\n return index !== -1;\n }\n return false;\n });\n });\n\n // @ts-ignore\n return products\n .filter((el) => \"details\" in el)\n .map((el) => {\n el.details = resolveProductDetailsFromSingleProduct(\n el.productID,\n el.details as SingleProduct,\n );\n return el;\n });\n};\n\nexport const useCartProvider = () => {\n const context = useContext(CartProviderContext);\n\n if (context === undefined) {\n throw new Error(\"useCartProvider must be used within a CartProvider\");\n }\n\n return context;\n};\n\nexport default CartProvider;\n\nconst googlePayConfigSchema = zod.object({\n isAvailable: zod.boolean(),\n merchantId: zod.string(),\n merchantName: zod.string(),\n gateway: zod.string(),\n gatewayId: zod.string(),\n});\nconst checkoutSchema = zod.object({\n country: zod.string().optional(),\n couponCode: zod.string().optional(),\n selectedDeliveryMethod: zod.string().optional(),\n postalCode: zod.string().optional(),\n email: zod.string().optional(),\n products: zod\n .array(\n zod.object({\n id: zod.string(),\n productID: zod.string(),\n quantity: zod.number(),\n metadata: zod.record(zod.string(), zod.string().optional()).optional(),\n }),\n )\n .optional(),\n googlePayConfig: googlePayConfigSchema.optional(),\n});\nexport type GooglePayConfig = zod.infer<typeof googlePayConfigSchema>;\nconst emptyCheckout: Checkout = {\n country: undefined,\n couponCode: undefined,\n email: undefined,\n selectedDeliveryMethod: undefined,\n postalCode: undefined,\n products: [],\n googlePayConfig: undefined,\n};\nexport type Checkout = zod.infer<typeof checkoutSchema>;\n\nconst checkoutKey = \"checkout\";\n\nfunction getCheckout(): Checkout {\n try {\n const value = localStorage.getItem(checkoutKey);\n if (value == null) {\n return emptyCheckout;\n }\n return checkoutSchema.parse(JSON.parse(value));\n } catch (e) {\n return emptyCheckout;\n }\n}\n\nfunction addProducts(products: CartProduct[]) {\n const checkout = getCheckout();\n if (checkout.products == null) {\n checkout.products = [];\n }\n checkout.products.push(...products.map(el => ({\n id: Math.random().toString(36).substring(7),\n productID: el.productID,\n quantity: el.quantity,\n metadata: el.metadata,\n })));\n saveCheckout(checkout);\n}\n\nfunction removeProductFromCheckout(ids: string[]) {\n const checkout = getCheckout();\n const newCheckout = {\n ...checkout,\n products: checkout?.products?.filter(\n (product) => !ids.includes(product.productID) && !ids.includes(product.id),\n ),\n };\n saveCheckout(newCheckout);\n}\n\nfunction saveCheckout(model: Checkout) {\n localStorage.setItem(checkoutKey, JSON.stringify(model));\n}\n\nexport const getCheckoutMethods: (projectID: string) => CartMethods = (\n projectID,\n) => {\n const newHost = getNewHostIfExist();\n if (newHost != null) {\n setCustomHost(newHost);\n }\n return {\n async checkIfApplePayIsAvailable(): Promise<boolean> {\n if (typeof window == \"undefined\" || typeof document == 'undefined') {\n return false;\n }\n if (\"ApplePaySession\" in window === false) {\n return false;\n }\n\n try {\n const result = await PublicApiClient.get(HOST).isOneClickPaymentAvailable({\n projectId: projectID,\n });\n\n return result.applePay;\n } catch (e) {\n console.error(\"Error checking if Apple Pay is available\", e);\n return false;\n }\n },\n async getGooglePayConfiguration(args: { items: CartProduct[] }): Promise<{\n isAvailable: true;\n config: google.payments.api.PaymentDataRequest;\n } | {\n isAvailable: false;\n }> {\n if (typeof window == \"undefined\" || typeof document == 'undefined') {\n return {\n isAvailable: false,\n }\n }\n\n try {\n const googlePayConfig = await resolveGooglePayConfiguration(projectID);\n if (googlePayConfig?.isAvailable != true) {\n return {\n isAvailable: false,\n }\n }\n\n const checkout = getCheckout();\n if (checkout == null) {\n console.log('checkout is null');\n return {\n isAvailable: false,\n }\n }\n if (args && args.items != null && args.items.length > 0) {\n if (checkout.products) {\n removeProductFromCheckout(checkout.products.map(el => el.productID));\n }\n addProducts(args.items);\n }\n\n const products = await getProducts(projectID);\n\n if (isErr(products)) {\n return {\n isAvailable: false,\n };\n }\n const result = await PublicApiClient.get(HOST).getPreOrderDetails({\n items: products.map((el) =>\n create(PreOrderItemSchema, { id: el.productID, quantity: el.quantity }),\n ),\n projectId: projectID,\n // we can detect 5 the closest inpost pickup point based on shipping address.\n postCode: checkout.postalCode,\n couponCode: checkout.couponCode,\n countryCode: checkout.country,\n selectedDeliveryMethod: checkout.selectedDeliveryMethod,\n });\n if (result.totalAmount == null) {\n return {\n isAvailable: false,\n }\n }\n const cartRequiresShipping = products.find(el => el.details?.isDeliverable == true) != null;\n\n const mappedPaymentRequest = await mapGooglePayConfiguration({\n items: products,\n googlePayConfig: googlePayConfig,\n checkout: checkout,\n cartRequiresShipping: cartRequiresShipping,\n result: result,\n });\n if (mappedPaymentRequest == null) {\n return {\n isAvailable: false,\n }\n }\n\n return {\n isAvailable: true,\n config: mappedPaymentRequest,\n }\n } catch (e) {\n console.error(\"Error resolving Google Pay configuration\", e);\n return {\n isAvailable: false,\n }\n }\n },\n async clearCart(): Promise<Err | void> {\n const empty = structuredClone(emptyCheckout);\n empty.country = getCheckout()?.country;\n saveCheckout(empty);\n },\n\n async getAvailableCountries(): Promise<\n Err | { name: string; code: string }[]\n > {\n const result = await fetch(buildURL(projectID, \"/countries\"), {\n method: \"GET\",\n });\n\n if (result.status != 200) {\n return [];\n }\n\n try {\n return zod\n .array(\n zod.object({\n name: zod.string(),\n code: zod.string(),\n }),\n )\n .parse(await result.json());\n } catch (e) {\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-expect-error\n return Err(e.toString(), \"internal\");\n }\n },\n\n async getCountry(): Promise<Err | string> {\n let country = getCheckout()?.country;\n if (country == null) {\n country = \"PL\";\n const checkout = getCheckout();\n checkout.country = country;\n saveCheckout(checkout);\n }\n\n return country;\n },\n\n async getNumberOfElementsInCart(): Promise<number> {\n return (\n getCheckout()?.products?.reduce(\n (acc, product) => acc + product.quantity,\n 0,\n ) ?? 0\n );\n },\n\n async getProducts(): Promise<\n | Err\n | {\n id: string;\n productID: string;\n quantity: number;\n metadata?: {\n [p: string]: string | undefined\n };\n details?: SingleProduct;\n }[]\n > {\n return await getProducts(projectID);\n },\n\n async removeProductFromCart(args: { id: string }): Promise<Err | void> {\n return removeProductFromCheckout([args.id]);\n },\n\n async setCountry(country: string): Promise<Err | void> {\n const checkout = getCheckout();\n checkout.country = country;\n saveCheckout(checkout);\n },\n\n async setCouponCodeIfPossible(couponCode?: string): Promise<Err | void> {\n const checkout = getCheckout();\n checkout.couponCode = couponCode;\n saveCheckout(checkout);\n },\n\n async setPostalCode(postalCode?: string): Promise<Err | void> {\n const checkout = getCheckout();\n checkout.postalCode = postalCode;\n saveCheckout(checkout);\n },\n\n async setDeliveryMethod(deliveryMethod?: string): Promise<Err | void> {\n const checkout = getCheckout();\n checkout.selectedDeliveryMethod = deliveryMethod;\n saveCheckout(checkout);\n },\n\n async setEmail(email?: string): Promise<Err | void> {\n const checkout = getCheckout();\n checkout.email = email;\n saveCheckout(checkout);\n },\n\n async addProduct(args: CartProduct): Promise<Err | void> {\n return addProducts([args]);\n },\n\n async updateQuantity(args: {\n id: string;\n quantity: number;\n }): Promise<Err | void> {\n const checkout = getCheckout();\n checkout.products = checkout.products?.map((product) => {\n if (product.id === args.id) {\n product.quantity = args.quantity;\n }\n return product;\n });\n saveCheckout(checkout);\n },\n\n async redirectToNextStep(): Promise<Err | { url: string }> {\n const checkout = getCheckout();\n\n if (checkout.products == null || checkout.products.length == 0) {\n return Err(\"cart-is-empty\", \"badInput\");\n }\n const mergedMetadata = checkout.products.reduce(\n (acc, product) => {\n if (product.metadata != null) {\n Object.keys(product.metadata).forEach((key) => {\n const value = product.metadata?.[key];\n if (value != null) {\n acc[key] = value;\n }\n });\n }\n return acc;\n },\n {} as Record<string, string>,\n );\n\n const payload: LinkRequest = {\n items: checkout.products.map((el) => {\n return {\n id: el.productID,\n quantity: el.quantity,\n };\n }),\n askForNote: true,\n countries: checkout.country ? [checkout.country] : undefined,\n metadata: mergedMetadata,\n };\n\n const paymentResponse = await createPaymentURL(payload);\n\n if (isErr(paymentResponse)) {\n console.warn(\"cannot create payment URL\", paymentResponse.error);\n return paymentResponse;\n }\n\n return {\n url: paymentResponse.url,\n };\n },\n\n async justRedirectToPayment(args: {\n email?: string;\n productID: string;\n name?: string;\n country?: string;\n marketingAgreement?: boolean;\n }): Promise<Err | { url: string }> {\n let payload: LinkRequest = {\n items: [\n {\n id: args.productID,\n quantity: 1,\n },\n ],\n askForNote: false,\n details:\n args.email || args.name\n ? {\n name: args.name,\n email: args.email,\n }\n : undefined,\n };\n\n payload = injectReferenceToRequestIfNeeded(payload);\n\n if (args.country == null) {\n args.country = \"pl\";\n }\n\n if (payload.details == null) {\n payload.details = {};\n }\n if (payload.details.address == null) {\n payload.details.address = {};\n }\n payload.details.address.country = args.country;\n\n if (args.marketingAgreement === true) {\n payload.additionalAgreements = [\n {\n id: MARKETING_AGREEMENT_ID,\n name: \"\",\n required: true,\n answer: true,\n },\n ];\n }\n\n const result = await fetch(buildURL(projectID, \"/buy\"), {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(payload),\n });\n console.log(\"CODE\", result.status);\n\n if (result.status != 200) {\n console.warn(\n \"result\",\n await result.text(),\n result.status,\n result.statusText,\n );\n return Err(\"internal-error\", \"internal\");\n }\n\n const response: { url: string; sessionID: string } = await result.json();\n\n console.log(response.url);\n return {\n url: response.url,\n };\n },\n\n async completeApplePayPayment(args: {\n token: ApplePayJS.ApplePayPaymentToken;\n shippingContact?: ApplePayJS.ApplePayPaymentContact;\n billingContact?: ApplePayJS.ApplePayPaymentContact;\n }): Promise<\n | Err\n | {\n isSuccess: boolean;\n redirectURL?: string;\n }\n > {\n const checkout = getCheckout();\n if (checkout == null) {\n return Err(\"Checkout not found\");\n }\n\n const products = await getProducts(projectID);\n\n if (isErr(products)) {\n return products;\n }\n\n try {\n let shippingName = args.shippingContact?.givenName;\n if (args.shippingContact?.familyName != null) {\n shippingName += ' ' + args.shippingContact?.familyName;\n } else if (args.billingContact?.familyName != null) {\n shippingName += ' ' + args.billingContact?.familyName;\n }\n\n let metadata: Record<string, string> = {};\n\n const ref = getReferenceIfPossible();\n if (ref != null) {\n metadata[\"ref\"] = ref;\n }\n\n const result = await PublicApiClient.get(HOST).authorizeOneClickPayment(\n {\n gateway: OneClickAuthorizePaymentRequest_Gateway.APPLE_PAY,\n paymentData: JSON.stringify(args.token.paymentData),\n paymentMethod: JSON.stringify(args.token.paymentMethod),\n transactionIdentifier: args.token.transactionIdentifier,\n order: create(GetPreOrderDetailsRequestSchema, {\n items: products.map((el) => ({\n id: el.productID,\n quantity: el.quantity,\n })),\n couponCode: checkout.couponCode,\n email: args.shippingContact?.emailAddress ?? args.billingContact?.emailAddress ?? checkout.email ?? undefined,\n phone: args.shippingContact?.phoneNumber ?? args.billingContact?.phoneNumber ?? undefined,\n selectedDeliveryMethod: checkout.selectedDeliveryMethod,\n postCode: checkout.postalCode,\n projectId: projectID,\n }),\n shippingAddress: create(AddressSchema, {\n line1: args.shippingContact?.addressLines?.[0] ?? \"\",\n city: args.shippingContact?.locality ?? \"\",\n country: args.shippingContact?.countryCode ?? \"\",\n zipCode: args.shippingContact?.postalCode ?? \"\",\n name: shippingName,\n }),\n billingAddress: create(BillingAddressSchema, {\n name: args.billingContact?.givenName ?? '' + ' ' + (args.billingContact?.familyName ?? ''),\n line1: args.billingContact?.addressLines?.[0] ?? \"\",\n city: args.billingContact?.locality ?? \"\",\n country: args.billingContact?.countryCode ?? \"\",\n zipCode: args.billingContact?.postalCode ?? \"\",\n }),\n metadata: Object.keys(metadata).length > 0 ? JSON.stringify(metadata) : undefined,\n },\n );\n\n return {\n isSuccess: result.isSuccess,\n redirectURL: result.redirectUrl,\n };\n } catch (error) {\n console.error(error);\n return Err(\"Failed to authorize payment\");\n }\n },\n\n async completeGooglePayPayment(args: {\n paymentData: google.payments.api.PaymentData\n }): Promise<Err | {\n isSuccess: boolean;\n redirectURL?: string;\n }> {\n const checkout = getCheckout();\n if (checkout == null) {\n return Err(\"Checkout not found\");\n }\n\n const products = await getProducts(projectID);\n\n if (isErr(products)) {\n return products;\n }\n\n try {\n let shippingName = args.paymentData.shippingAddress?.name;\n\n let metadata: Record<string, string> = {};\n\n const ref = getReferenceIfPossible();\n if (ref != null) {\n metadata[\"ref\"] = ref;\n }\n\n const result = await PublicApiClient.get(HOST).authorizeOneClickPayment(\n {\n gateway: OneClickAuthorizePaymentRequest_Gateway.GOOGLE_PAY,\n paymentData: args.paymentData.paymentMethodData.tokenizationData.token,\n paymentMethod: args.paymentData.paymentMethodData.type,\n transactionIdentifier: '',\n order: create(GetPreOrderDetailsRequestSchema, {\n items: products.map((el) => ({\n id: el.productID,\n quantity: el.quantity,\n })),\n couponCode: checkout.couponCode,\n email: args.paymentData.email ?? undefined,\n phone: args.paymentData.shippingAddress?.phoneNumber ?? undefined,\n selectedDeliveryMethod: args.paymentData.shippingOptionData?.id ?? checkout.selectedDeliveryMethod,\n postCode: args.paymentData.shippingAddress?.postalCode,\n projectId: projectID,\n }),\n shippingAddress: create(AddressSchema, {\n line1: args.paymentData.shippingAddress?.address1 ?? \"\",\n city: args.paymentData.shippingAddress?.locality ?? \"\",\n country: args.paymentData.shippingAddress?.countryCode ?? \"\",\n zipCode: args.paymentData.shippingAddress?.postalCode ?? \"\",\n name: shippingName,\n }),\n billingAddress: create(BillingAddressSchema, {\n name: args.paymentData.paymentMethodData.info?.billingAddress?.name,\n line1: args.paymentData.paymentMethodData.info?.billingAddress?.address1 ?? \"\",\n city: args.paymentData.paymentMethodData.info?.billingAddress?.locality ?? \"\",\n country: args.paymentData.paymentMethodData.info?.billingAddress?.countryCode ?? \"\",\n zipCode: args.paymentData.paymentMethodData.info?.billingAddress?.postalCode ?? \"\",\n }),\n metadata: Object.keys(metadata).length > 0 ? JSON.stringify(metadata) : undefined,\n },\n );\n\n return {\n isSuccess: result.isSuccess,\n redirectURL: result.redirectUrl,\n };\n } catch (error) {\n console.error(error);\n return Err(\"Failed to authorize payment\");\n }\n\n return {\n isSuccess: true,\n }\n },\n\n async getApplePayPaymentRequest(args?: { expectedItems?: CartProduct[]; }): Promise<\n Err | ApplePayJS.ApplePayPaymentRequest\n > {\n const checkout = getCheckout();\n if (checkout == null) {\n return Err(\"Checkout not found\");\n }\n if (args && args.expectedItems != null && args.expectedItems.length > 0) {\n if (checkout.products) {\n removeProductFromCheckout(checkout.products.map(el => el.productID));\n }\n addProducts(args.expectedItems);\n }\n\n const products = await getProducts(projectID);\n\n if (isErr(products)) {\n return products;\n }\n\n const result = await PublicApiClient.get(HOST).getPreOrderDetails({\n items: products.map((el) =>\n create(PreOrderItemSchema, { id: el.productID, quantity: el.quantity }),\n ),\n projectId: projectID,\n // we can detect 5 the closest inpost pickup point based on shipping address.\n postCode: checkout.postalCode,\n couponCode: checkout.couponCode,\n countryCode: checkout.country,\n selectedDeliveryMethod: checkout.selectedDeliveryMethod,\n });\n\n const items = products.map((el) => {\n return {\n amount: `${round((el.quantity * (el.details?.price ?? 0)) / 100, 2)}`,\n label: el.details?.name + \" - \" + el.quantity,\n type: \"final\" as ApplePayJS.ApplePayLineItemType,\n };\n });\n\n if (result.selectedDeliveryMethod != null) {\n const deliveryMethod = result.methods.find(el => el.id == result.selectedDeliveryMethod);\n if (deliveryMethod != null) {\n items.push({\n amount: `${round(deliveryMethod.amount / 100, 2)}`,\n label: deliveryMethod.name,\n type: \"final\" as ApplePayJS.ApplePayLineItemType,\n })\n }\n }\n\n if (result.couponCode != null && result.couponValue != null) {\n items.push({\n amount: `-${round(result.couponValue / 100, 2)}`,\n label: result.couponCode,\n type: \"final\" as ApplePayJS.ApplePayLineItemType,\n });\n }\n\n return {\n countryCode: checkout.country ?? \"PL\",\n merchantCapabilities: [\n \"supports3DS\",\n \"supportsCredit\",\n \"supportsDebit\",\n ],\n supportedNetworks: [\"visa\", \"masterCard\"],\n total: {\n amount: `${round(((result.totalAmount ?? 0) / 100), 2)}`,\n label: \"Płatność za koszyk\",\n type: \"final\",\n },\n shippingMethods: result.methods.map((el) => ({\n amount: `${round(el.amount / 100, 2)}`,\n detail: el.description ?? \"\",\n identifier: el.id,\n label: el.name,\n dateComponentsRange: {\n startDateComponents: {\n days: el.minDays ?? 1,\n hours: 0,\n months: 0,\n years: 0,\n } satisfies ApplePayJS.ApplePayDateComponents,\n endDateComponents: {\n days: el.maxDays ?? 14,\n hours: 0,\n months: 0,\n years: 0,\n } satisfies ApplePayJS.ApplePayDateComponents,\n },\n })),\n lineItems: items,\n currencyCode: \"PLN\",\n };\n },\n };\n};\n\nasync function resolveGooglePayConfiguration(projectID: string) {\n const checkout = getCheckout();\n if (checkout.googlePayConfig?.isAvailable != true) {\n const result = await PublicApiClient.get(HOST).isOneClickPaymentAvailable({\n projectId: projectID,\n });\n if (result.googlePay == true && result.googlePayConfig != null) {\n checkout.googlePayConfig = {\n isAvailable: true,\n gateway: result.googlePayConfig.gateway,\n gatewayId: result.googlePayConfig.gatewayMerchantId,\n merchantName: result.googlePayConfig.merchantName,\n merchantId: result.googlePayConfig.merchantId,\n }\n } else {\n checkout.googlePayConfig = {\n isAvailable: false,\n gateway: '',\n gatewayId: '',\n merchantName: '',\n merchantId: '',\n }\n }\n }\n saveCheckout(checkout);\n return checkout.googlePayConfig;\n}\n\nfunction resolveProductDetailsFromSingleProduct(\n id: string,\n product: SingleProduct,\n) {\n if (id == product.id) {\n const variantIndex = product.variants?.findIndex((v) => v.id === id);\n if (variantIndex != null && variantIndex != -1) {\n const variant = product.variants![variantIndex];\n return {\n ...product,\n availableQuantity: variant.availableQuantity,\n isDeliverable: variant.isDeliverable,\n images: variant.images ?? product.images,\n name: product.name + \" \" + variant.name,\n price: variant.price,\n currency: variant.currency\n }\n }\n return product;\n }\n\n const variant = product.variants?.find((v) => v.id === id);\n if (variant == null) {\n return product;\n }\n\n return {\n ...product,\n id: variant.id,\n name: product.name + \" \" + variant.name,\n price: variant.price,\n currency: variant.currency,\n metadata: product.metadata,\n availableQuantity: variant.availableQuantity,\n isDeliverable: variant.isDeliverable,\n images: variant.images ?? product.images,\n };\n}\n\ntype ApplePaySessionArgs = {\n postCode?: string;\n email?: string;\n couponCode?: string;\n};\n","export let HOST = \"https://bitsnap.pl\";\n\nexport function setCustomHost(host: string) {\n HOST = host;\n}\n","import { HOST } from \"./constants\";\n\nexport function buildURL(projectID: string, path: string): string {\n return `${HOST}/api/integrations/${projectID}/public-commerce${path}`;\n}\n","// eslint-disable-next-line @typescript-eslint/ban-ts-comment\n// @ts-ignore\nexport type Err = {\n ERR: true\n error: unknown\n type?: ErrTypes\n}\n\ntype ErrTypes = 'internal' | 'badInput' | 'notFound';\n\nexport function isErr(x: unknown): x is Err {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return typeof x === 'object' && x != null && 'ERR' in x;\n}\n\nexport function Err(message: string, type?: ErrTypes): Err {\n return { ERR: true, error: message, type: type }\n}\n\nexport async function tryFail<T>(\n f: (() => Promise<T>) | (() => T)\n): Promise<T | Err> {\n try {\n return await f()\n } catch (e) {\n return { ERR: true, error: e }\n }\n}\n\nexport function assertOk<T>(x: T | Err) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n if (isErr(x)) throw Error((x.error as any).toString());\n}\n","export function round(num: number, numberOfDecimals: number = 2): number {\n return Number(\n +(\n Math.round(Number(num + \"e+\" + numberOfDecimals)) +\n \"e-\" +\n numberOfDecimals\n )\n );\n}\n\nexport function formatCurrency(amount: number, currency: string): string {\n const formatter = Intl.NumberFormat(navigator.language, {\n style: \"currency\",\n currency: currency,\n currencyDisplay: 'symbol',\n });\n\n return formatter.format(amount / 100);\n}","import { create } from 'zustand';\n\ninterface CheckoutStore {\n isCartVisible: boolean;\n numberOfProductsInCart: number;\n showCart: () => void;\n hideCart: () => void;\n setNumberOfProductsInCart: (numberOfProductsInCart: number) => void;\n}\n\nexport const useCheckoutStore = create<CheckoutStore>((set) => ({\n isCartVisible: false,\n numberOfProductsInCart: 2,\n showCart: () => set((state) => ({ ...state, isCartVisible: true })),\n hideCart: () => set((state) => ({ ...state, isCartVisible: false })),\n setNumberOfProductsInCart: (numberOfProductsInCart: number) => set((state) => ({ ...state, numberOfProductsInCart: numberOfProductsInCart })),\n}))\n","import { getCheckoutMethods, getProjectID } from \"./CartProvider\";\nimport { HOST } from \"./constants\";\nimport { buildURL } from \"./helper.methods\";\nimport { Err } from \"./lib/err\";\nimport { LinkRequest } from \"./link.request.schema\";\nimport { useCheckoutStore } from \"./state\";\n\n// deprecated, use Bitsnap.addProductToCart()\nexport async function addProductToCart(\n id: string,\n quantity: number = 1,\n metadata?: Record<string, string | undefined>,\n) {\n return Bitsnap.addProductToCart(id, quantity, metadata);\n}\n\n// deprecated, use Bitsnap.showCart()\nexport function showCart() {\n return Bitsnap.showCart();\n}\n\n// deprecated, use Bitsnap.hideCart()\nexport function hideCart() {\n return Bitsnap.hideCart();\n}\n\nexport namespace Bitsnap {\n export async function addProductToCart(\n id: string,\n quantity: number = 1,\n metadata?: Record<string, string | undefined>,\n ) {\n const projectID = getProjectID();\n if (projectID == null) {\n throw new Error(\"No project ID found\");\n }\n\n const methods = getCheckoutMethods(projectID);\n\n const err = await methods.addProduct({\n productID: id,\n quantity: quantity,\n metadata: metadata,\n });\n if (err != null) {\n return err;\n }\n\n return undefined;\n }\n\n export function showCart() {\n useCheckoutStore.setState({ isCartVisible: true });\n }\n\n export function hideCart() {\n useCheckoutStore.setState({ isCartVisible: false });\n }\n}\n\nexport async function createPaymentURL(request: LinkRequest) {\n const projectID = getProjectID();\n if (projectID == null) {\n throw new Error(\"No project ID found\");\n }\n\n request = injectReferenceToRequestIfNeeded(request);\n\n const result = await fetch(buildURL(projectID, \"/buy\"), {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(request),\n });\n\n if (result.status != 200) {\n console.warn(\n \"result\",\n await result.text(),\n result.status,\n result.statusText,\n );\n return Err(\"internal-error\", \"internal\");\n }\n\n const response: { url: string; sessionID: string } = await result.json();\n\n return {\n url: response.url,\n };\n}\n\nexport async function createCheckout(\n request: LinkRequest & { apiKey?: string; testMode?: boolean },\n) {\n const projectID = getProjectID();\n if (projectID == null) {\n throw new Error(\"No project ID found\");\n }\n\n const headers = {\n \"Content-Type\": \"application/json\",\n ...(request.apiKey != null\n ? { Authorization: `Bearer ${request.apiKey}` }\n : {}),\n };\n\n const path = request.testMode\n ? `/api/payment/link/auto/${projectID}/test`\n : `/api/payment/link/auto/${projectID}`;\n\n delete request.apiKey;\n delete request.testMode;\n\n const response = await fetch(HOST + path, {\n method: \"POST\",\n headers,\n body: JSON.stringify(request),\n });\n\n const payload: {\n url: string;\n } = await response.json();\n\n return {\n status: \"ok\",\n redirectURL: payload.url,\n };\n}\n\nexport function getReferenceIfPossible(): string | undefined {\n if (typeof localStorage == \"undefined\") {\n return undefined;\n }\n const refLink = localStorage.getItem(\"bitsnap-ref\");\n if (refLink == null) {\n return undefined;\n }\n return refLink;\n}\n\nexport function injectReferenceToRequestIfNeeded(\n request: LinkRequest,\n): LinkRequest {\n const ref = getReferenceIfPossible();\n if (ref == null) {\n return request;\n }\n\n if (request.metadata == null) {\n request.metadata = {};\n }\n request.metadata[\"ref\"] = ref;\n return request;\n}\n","import { GetPreOrderDetailsResponse } from \"src/gen/proto/public/v1/public_api_pb\";\nimport { Checkout, GooglePayConfig } from \"./CartProvider\";\nimport { formatCurrency, round } from \"./lib/round.number\";\nimport { SingleProduct } from \"./product.details.model\";\n\nexport function mapGooglePayConfiguration(args: {\n items: {\n id: string;\n productID: string;\n quantity: number;\n metadata?: {\n [key: string]: string | undefined;\n };\n details?: SingleProduct;\n }[];\n googlePayConfig: GooglePayConfig;\n checkout: Checkout;\n result: GetPreOrderDetailsResponse;\n cartRequiresShipping: boolean;\n}): google.payments.api.PaymentDataRequest | undefined {\n const { items, googlePayConfig, checkout, result, cartRequiresShipping } = args;\n if (result.totalAmount == null) {\n return undefined;\n }\n let intents: google.payments.api.CallbackIntent[] = [];\n if (cartRequiresShipping) {\n intents.push('SHIPPING_ADDRESS');\n intents.push('SHIPPING_OPTION');\n }\n\n const returnValue: google.payments.api.PaymentDataRequest = {\n apiVersion: 2,\n apiVersionMinor: 0,\n callbackIntents: intents,\n allowedPaymentMethods: [\n {\n type: 'CARD',\n parameters: {\n allowedAuthMethods: ['PAN_ONLY', 'CRYPTOGRAM_3DS'],\n allowedCardNetworks: ['MASTERCARD', 'VISA'],\n },\n tokenizationSpecification: {\n type: 'PAYMENT_GATEWAY',\n parameters: {\n gateway: googlePayConfig.gateway,\n gatewayMerchantId: googlePayConfig.gatewayId,\n },\n },\n },\n ],\n emailRequired: true,\n shippingAddressParameters: {\n phoneNumberRequired: cartRequiresShipping,\n },\n shippingAddressRequired: cartRequiresShipping,\n shippingOptionRequired: cartRequiresShipping,\n shippingOptionParameters: cartRequiresShipping ? {\n shippingOptions: result.methods.slice(0, 5).map(method => ({\n id: method.id,\n label: method.name + ' - ' + `${formatCurrency(method.amount, result.currency)}`,\n description: (method.description ?? ''),\n })),\n defaultSelectedOptionId: result.selectedDeliveryMethod,\n } : undefined,\n merchantInfo: {\n merchantId: googlePayConfig.merchantId + '',\n merchantName: googlePayConfig.merchantName,\n },\n transactionInfo: {\n totalPriceStatus: 'FINAL',\n totalPriceLabel: 'Płatność za koszyk',\n totalPrice: `${round(result.totalAmount / 100, 2)}`,\n currencyCode: result.currency,\n countryCode: result.country,\n checkoutOption: 'COMPLETE_IMMEDIATE_PURCHASE',\n displayItems: mapGooglePayDisplayItems(items, result.methods, result.selectedDeliveryMethod ?? (result.methods.length > 0 ? result.methods[0].id : undefined), checkout.couponCode, result.couponValue),\n },\n }\n console.log('returnValue', returnValue);\n return returnValue;\n}\n\nfunction mapGooglePayDisplayItems(items: {\n id: string;\n productID: s