diff --git a/locksmith/src/controllers/v2/checkoutController.ts b/locksmith/src/controllers/v2/checkoutController.ts index 01b3a718bb4..f18d89769b2 100644 --- a/locksmith/src/controllers/v2/checkoutController.ts +++ b/locksmith/src/controllers/v2/checkoutController.ts @@ -6,6 +6,8 @@ import { deleteCheckoutConfigById, } from '../../operations/checkoutConfigOperations' import { PaywallConfig } from '@unlock-protocol/core' +import { Payload } from '../../models/payload' +import { addJob } from '../../worker/worker' /** * Create or update a checkout configuration. @@ -141,3 +143,160 @@ export const deleteCheckoutConfig: RequestHandler = async ( }) return } + +export const updateCheckoutHooks: RequestHandler = async ( + request, + response +) => { + const { id } = request.params + const userAddress = request.user!.walletAddress + const payload = request.body + + try { + const existingConfig = await getCheckoutConfigById(id) + + if (!existingConfig) { + response.status(404).send({ + message: 'No config found', + }) + return + } + + const updatedConfig = { + ...existingConfig.config, + hooks: { + ...existingConfig.config.hooks, + ...payload, + }, + } + + const checkoutConfig = await PaywallConfig.strip().parseAsync(updatedConfig) + + const storedConfig = await saveCheckoutConfig({ + id, + name: existingConfig.name, + config: checkoutConfig, + user: userAddress, + }) + + response.status(200).send({ + id: storedConfig.id, + by: storedConfig.createdBy, + name: storedConfig.name, + config: storedConfig.config, + updatedAt: storedConfig.updatedAt.toISOString(), + createdAt: storedConfig.createdAt.toISOString(), + }) + } catch (error) { + if ( + error instanceof Error && + error.message === 'User not authorized to update this configuration' + ) { + response.status(403).send({ + message: error.message, + }) + return + } + + if (error instanceof Error) { + response.status(400).send({ + message: 'Invalid hooks payload', + error: error.message, + }) + return + } + + throw error + } +} + +export const getCheckoutHookJobs: RequestHandler = async ( + request, + response +) => { + const userAddress = request.user!.walletAddress + + try { + const jobs = await Payload.findAll({ + where: { + payload: { + by: userAddress, + read: false, + }, + }, + order: [['createdAt', 'DESC']], + }) + + if (!jobs) { + response + .status(404) + .send({ message: 'No unread checkout hook jobs found for this user.' }) + return + } + + response.status(200).send(jobs) + } catch (error: any) { + response.status(400).send({ message: 'Could not retrieve jobs.' }) + } +} + +export const addCheckoutHookJob: RequestHandler = async (request, response) => { + const { id } = request.params + + try { + const checkout = await getCheckoutConfigById(id) + const payloadData = request.body + + const payload = new Payload() + payload.payload = { + checkoutId: id, + by: checkout?.by, + status: 'pending', + read: false, + ...payloadData, + } + await payload.save() + + const job = await addJob('checkoutHookJob', payload) + + response.status(200).send({ + message: 'Job added successfully', + job, + }) + } catch (error) { + response.status(400).send({ message: 'Could not add job.' }) + } +} + +export const updateCheckoutHookJob: RequestHandler = async ( + request, + response +) => { + const payloadId = request.params.id + const userAddress = request.user!.walletAddress + + try { + const job = await Payload.findByPk(payloadId) + if (!job) { + response.status(404).send({ message: 'No existing job found to update.' }) + return + } + + if (job.payload.by !== userAddress) { + response.status(403).send({ message: 'Not authorized to update job.' }) + } + + job.payload = { + ...job.payload, + read: true, + } + await job.save() + + response.status(200).send({ + message: 'Job marked as read successfully', + job, + }) + } catch (error) { + response.status(400).send({ message: 'Could not update job.' }) + } +} diff --git a/locksmith/src/routes/v2/checkoutConfigs.ts b/locksmith/src/routes/v2/checkoutConfigs.ts index f02ec4dcb38..4231d1d2373 100644 --- a/locksmith/src/routes/v2/checkoutConfigs.ts +++ b/locksmith/src/routes/v2/checkoutConfigs.ts @@ -5,10 +5,18 @@ import { createOrUpdateCheckoutConfig, deleteCheckoutConfig, getCheckoutConfigs, + updateCheckoutHooks, + getCheckoutHookJobs, + addCheckoutHookJob, + updateCheckoutHookJob, } from '../../controllers/v2/checkoutController' const router: express.Router = express.Router({ mergeParams: true }) router.get('/list', authenticatedMiddleware, getCheckoutConfigs) +router.get('/hooks/all', authenticatedMiddleware, getCheckoutHookJobs) +router.post('/hooks/:id', authenticatedMiddleware, addCheckoutHookJob) +router.put('/hooks/:id', authenticatedMiddleware, updateCheckoutHooks) +router.patch('/hooks/:id', authenticatedMiddleware, updateCheckoutHookJob) router.put('/:id?', authenticatedMiddleware, createOrUpdateCheckoutConfig) router.get('/:id', getCheckoutConfig) router.delete('/:id', authenticatedMiddleware, deleteCheckoutConfig) diff --git a/locksmith/src/worker/tasks/checkoutHooks.ts b/locksmith/src/worker/tasks/checkoutHooks.ts new file mode 100644 index 00000000000..995ebc92ce0 --- /dev/null +++ b/locksmith/src/worker/tasks/checkoutHooks.ts @@ -0,0 +1,38 @@ +import { Task } from 'graphile-worker' +import { Payload } from '../../models/payload' +import logger from '../../logger' +import axios from 'axios' +import { getCheckoutConfigById } from '../../operations/checkoutConfigOperations' + +export const checkoutHookJob: Task = async (payload: any) => { + const { id } = payload + const { checkoutId, event, data } = payload.payload + + const job = await Payload.findByPk(id) + + if (!job) { + logger.warn(`No job found with id ${id}`) + return + } + + const checkout: any = await getCheckoutConfigById(checkoutId) + const url = checkout?.config?.hooks && checkout.config.hooks[event] + + if (url) { + try { + await axios.post(url, data) + + job.payload = { + ...job.payload, + status: 'processed', + } + await job.save() + } catch (error) { + throw new Error('\nCould not send data to webhook') + } + } else { + throw new Error('\nurl not found') + } + + logger.info(`checkoutHookJob processed job with id ${id}`) +} diff --git a/locksmith/src/worker/worker.ts b/locksmith/src/worker/worker.ts index df96dfda60c..6960ca25e19 100644 --- a/locksmith/src/worker/worker.ts +++ b/locksmith/src/worker/worker.ts @@ -22,6 +22,7 @@ import { downloadReceipts } from './tasks/receipts' import { createEventCasterEvent } from './tasks/eventCaster/createEventCasterEvent' import { rsvpForEventCasterEvent } from './tasks/eventCaster/rsvpForEventCasterEvent' import exportKeysJob from './tasks/exportKeysJob' +import { checkoutHookJob } from './tasks/checkoutHooks' const crontabProduction = ` */5 * * * * monitor @@ -109,6 +110,7 @@ export async function startWorker() { downloadReceipts, createEventCasterEvent, rsvpForEventCasterEvent, + checkoutHookJob, }, }) diff --git a/packages/core/src/schema.ts b/packages/core/src/schema.ts index 505feae25a0..e6c29321a87 100644 --- a/packages/core/src/schema.ts +++ b/packages/core/src/schema.ts @@ -313,6 +313,30 @@ export const PaywallConfig = z claim: z.boolean().optional(), }) .optional(), + hooks: z + .object({ + status: z + .string({ + description: 'URL to be called on status change.', + }) + .optional(), + authenticated: z + .string({ + description: 'URL to be called when the user is authenticated.', + }) + .optional(), + transactionSent: z + .string({ + description: 'URL to be called when a transaction is sent.', + }) + .optional(), + metadata: z + .string({ + description: 'URL to be called for metadata updates.', + }) + .optional(), + }) + .optional(), }) .passthrough() diff --git a/packages/unlock-js/openapi.yml b/packages/unlock-js/openapi.yml index 3a19e448f30..068bad2ce61 100644 --- a/packages/unlock-js/openapi.yml +++ b/packages/unlock-js/openapi.yml @@ -3965,6 +3965,229 @@ paths: 401: $ref: '#/components/responses/401.NotAuthenticated' + /v2/checkout/hooks/all: + get: + operationId: getCheckoutHookJobs + security: + - User: [] + description: Get all unread checkout hook jobs for the currently authenticated user. + responses: + 200: + description: Array of unread hook jobs for all checkout configs by the current authenticated user. + content: + application/json: + schema: + type: array + items: + type: object + properties: + id: + type: string + createdAt: + type: string + updatedAt: + type: string + payload: + type: object + 400: + description: Could not retrieve jobs. + content: + application/json: + schema: + type: object + properties: + message: + type: string + 404: + description: No unread checkout hook jobs found for this user. + content: + application/json: + schema: + type: object + properties: + message: + type: string + + /v2/checkout/hooks/{id}: + post: + operationId: addCheckoutHookJob + security: + - User: [] + description: Add a new checkout hook job for the given checkout configuration, if the authenticated user owns it. + parameters: + - name: id + in: path + required: true + description: The id of the checkout config. + schema: + type: string + requestBody: + description: The payload for the hook job. + required: true + content: + application/json: + schema: + type: object + additionalProperties: true + responses: + 200: + description: Job added successfully. + content: + application/json: + schema: + type: object + properties: + message: + type: string + example: 'Job added successfully' + job: + type: object + 400: + description: Could not add job. + content: + application/json: + schema: + type: object + properties: + message: + type: string + + put: + operationId: updateCheckoutHooks + security: + - User: [] + description: Update the hooks object of a checkout config + parameters: + - name: id + in: path + required: true + description: The id of the checkout config to update + schema: + type: string + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + status: + type: string + description: URL to call when checkout status changes + metadata: + type: string + description: URL to call to fetch metadata + authenticated: + type: string + description: URL to call on successful user authentication + transactionSent: + type: string + description: URL to call when a transaction is sent + additionalProperties: false + responses: + 200: + description: Successfully updated the hooks for the given checkout config. + content: + application/json: + schema: + $ref: '#/components/schemas/CheckoutConfig' + 400: + description: Invalid hooks payload or user not authorized to update the config. + content: + application/json: + schema: + type: object + properties: + message: + type: string + error: + type: string + 401: + description: User is not authenticated. + content: + application/json: + schema: + type: object + properties: + message: + type: string + 403: + description: User not authorized to update this configuration. + content: + application/json: + schema: + type: object + properties: + message: + type: string + 404: + description: No checkout configuration found for the given id. + content: + application/json: + schema: + type: object + properties: + message: + type: string + + patch: + operationId: updateCheckoutHookJob + security: + - User: [] + description: Mark a specific checkout hook job as read. + parameters: + - name: id + in: path + required: true + description: The job ID to mark as read. + schema: + type: string + responses: + 200: + description: Job marked as read successfully. + content: + application/json: + schema: + type: object + properties: + message: + type: string + example: 'Job marked as read successfully' + job: + type: object + additionalProperties: true + properties: + read: + type: boolean + example: true + 400: + description: Could not update job. + content: + application/json: + schema: + type: object + properties: + message: + type: string + 403: + description: Not authorized to update job. + content: + application/json: + schema: + type: object + properties: + message: + type: string + 404: + description: No existing job found to update. + content: + application/json: + schema: + type: object + properties: + message: + type: string + /v2/purchase/payment-methods: delete: operationId: removePaymentMethods diff --git a/unlock-app/src/__tests__/hooks/useCheckoutCommunication.test.ts b/unlock-app/src/__tests__/hooks/useCheckoutCommunication.test.ts index 4d77fbb12f9..a7b69b3426a 100644 --- a/unlock-app/src/__tests__/hooks/useCheckoutCommunication.test.ts +++ b/unlock-app/src/__tests__/hooks/useCheckoutCommunication.test.ts @@ -113,19 +113,19 @@ describe('useCheckoutCommunication', () => { await waitFor(() => result.current.ready) // Once the emitter is ready, the buffer is flushed in the order events were received - expect(emit).toHaveBeenNthCalledWith(1, CheckoutEvents.userInfo, userInfo) - - expect(emit).toHaveBeenNthCalledWith( - 2, - CheckoutEvents.transactionInfo, - transactionInfo - ) - - expect(emit).toHaveBeenNthCalledWith( - 3, - CheckoutEvents.closeModal, - undefined - ) + await waitFor(() => { + expect(emit).toHaveBeenNthCalledWith(1, CheckoutEvents.userInfo, userInfo) + expect(emit).toHaveBeenNthCalledWith( + 2, + CheckoutEvents.transactionInfo, + transactionInfo + ) + expect(emit).toHaveBeenNthCalledWith( + 3, + CheckoutEvents.closeModal, + undefined + ) + }) }) }) diff --git a/unlock-app/src/components/interface/HooksNotifications.tsx b/unlock-app/src/components/interface/HooksNotifications.tsx new file mode 100644 index 00000000000..6077868520a --- /dev/null +++ b/unlock-app/src/components/interface/HooksNotifications.tsx @@ -0,0 +1,49 @@ +import dayjs from 'dayjs' +import { useState } from 'react' +import { locksmith } from '~/config/locksmith' + +export const Hooks = ({ + hooks, + refetch, +}: { + hooks: any[] + refetch: () => void +}) => { + const [removingId, setRemovingId] = useState(null) + + const deleteNotification = async (id: string) => { + setRemovingId(id) + setTimeout(async () => { + await locksmith.updateCheckoutHookJob(id) + refetch() + }, 300) + } + + return ( +
+

Hooks

+ {hooks.map((hook: any) => { + const { status, event } = hook.payload + const isPending = status === 'pending' + const text = isPending + ? `could not send ${event} event data` + : `sent ${event} event data successfully` + return ( +
deleteNotification(hook.id)} + key={hook.id} + className={`bg-white p-4 rounded-2xl text-sm border-2 my-2 transition-all duration-300 ease-in-out cursor-pointer + outline outline-1 ${isPending ? 'outline-red-300' : 'outline-green-300'} + hover:outline-[4px] + ${removingId === hook.id ? 'opacity-0' : 'opacity-100'}`} + > +

{text}

+

+ {dayjs(hook.createdAt).format('D MMMM YYYY - HH:mm')} +

+
+ ) + })} +
+ ) +} diff --git a/unlock-app/src/components/interface/checkout/CheckoutContainer.tsx b/unlock-app/src/components/interface/checkout/CheckoutContainer.tsx index a29534b929e..4595c1fb46a 100644 --- a/unlock-app/src/components/interface/checkout/CheckoutContainer.tsx +++ b/unlock-app/src/components/interface/checkout/CheckoutContainer.tsx @@ -13,9 +13,15 @@ import { ethers } from 'ethers' import { PaywallConfigType } from '@unlock-protocol/core' import { Connect } from './Connect' import { isInIframe } from '~/utils/iframe' +import { useEffect } from 'react' +import { config } from '~/config/app' +import { postToWebhook } from './main/checkoutHookUtils' +import { useAuthenticate } from '~/hooks/useAuthenticate' +declare const window: any export function CheckoutContainer() { const searchParams = useSearchParams() + const { account } = useAuthenticate() // Fetch config from parent in iframe context const communication = useCheckoutCommunication() @@ -52,6 +58,40 @@ export function CheckoutContainer() { })?.[1] ?.toString() + // prevents posting twice when this re-renders + let prevBody: string + useEffect(() => { + let script: any + let handler: any + + if (paywallConfig && account) { + handler = window.addEventListener( + 'unlockProtocol.status', + async (state: any) => { + const body = state.detail + if (JSON.stringify(body) === prevBody) { + return + } else { + prevBody = JSON.stringify(body) + postToWebhook(body, paywallConfig, 'status') + } + } + ) + + window.unlockProtocolConfig = paywallConfig + + script = document.createElement('script') + script.src = `${config.paywallUrl}/static/unlock.latest.min.js` + script.async = true + document.body.appendChild(script) + } + + return () => { + script?.remove() + window.removeEventListener('unlockProtocol', handler) + } + }, [paywallConfig]) + if (!(paywallConfig || oauthConfig) || isLoading) { return } diff --git a/unlock-app/src/components/interface/checkout/Connect/ConfirmConnect.tsx b/unlock-app/src/components/interface/checkout/Connect/ConfirmConnect.tsx index 0d587d39711..65a33b0fbb6 100644 --- a/unlock-app/src/components/interface/checkout/Connect/ConfirmConnect.tsx +++ b/unlock-app/src/components/interface/checkout/Connect/ConfirmConnect.tsx @@ -35,11 +35,14 @@ export function ConfirmConnect({ s: signature, }) ).toString('base64') - communication?.emitUserInfo({ - address: account, - message: message, - signedMessage: signature, - }) + communication?.emitUserInfo( + { + address: account, + message: message, + signedMessage: signature, + }, + paywallConfig + ) onClose({ code, state: oauthConfig.state, diff --git a/unlock-app/src/components/interface/checkout/main/Confirm.tsx b/unlock-app/src/components/interface/checkout/main/Confirm.tsx index 9eb9f2715ca..854a889b467 100644 --- a/unlock-app/src/components/interface/checkout/main/Confirm.tsx +++ b/unlock-app/src/components/interface/checkout/main/Confirm.tsx @@ -31,17 +31,23 @@ export function Confirm({ checkoutService, communication }: Props) { // If not pessimistic, we can emit the transaction info right away // and pass the signed message as well if (!paywallConfig.pessimistic && hash) { - communication?.emitTransactionInfo({ - hash, - lock, - metadata, - network, - }) - communication?.emitUserInfo({ - address: account, - signedMessage: messageToSign?.signature, - }) - communication?.emitMetadata(metadata) + communication?.emitTransactionInfo( + { + hash, + lock, + metadata, + network, + }, + paywallConfig + ) + communication?.emitUserInfo( + { + address: account, + signedMessage: messageToSign?.signature, + }, + paywallConfig + ) + communication?.emitMetadata(metadata, paywallConfig) } checkoutService.send({ type: 'CONFIRM_MINT', diff --git a/unlock-app/src/components/interface/checkout/main/MessageToSign.tsx b/unlock-app/src/components/interface/checkout/main/MessageToSign.tsx index 53807349008..f61dd4816e0 100644 --- a/unlock-app/src/components/interface/checkout/main/MessageToSign.tsx +++ b/unlock-app/src/components/interface/checkout/main/MessageToSign.tsx @@ -16,10 +16,11 @@ interface Props { } export function MessageToSign({ checkoutService, communication }: Props) { - const { messageToSign } = useSelector( + const paywallConfig = useSelector( checkoutService, (state) => state.context.paywallConfig ) + const messageToSign = paywallConfig.messageToSign const { account } = useAuthenticate() const { getWalletService } = useProvider() const [isSigning, setIsSigning] = useState(false) @@ -39,11 +40,14 @@ export function MessageToSign({ checkoutService, communication }: Props) { signature, address: account!, }) - communication?.emitUserInfo({ - address: account, - message: messageToSign, - signedMessage: signature, - }) + communication?.emitUserInfo( + { + address: account, + message: messageToSign, + signedMessage: signature, + }, + paywallConfig + ) } catch (error) { if (error instanceof Error) { ToastHelper.error(error.message) diff --git a/unlock-app/src/components/interface/checkout/main/Minting.tsx b/unlock-app/src/components/interface/checkout/main/Minting.tsx index b97b0366eb0..4d86f72287f 100644 --- a/unlock-app/src/components/interface/checkout/main/Minting.tsx +++ b/unlock-app/src/components/interface/checkout/main/Minting.tsx @@ -172,17 +172,23 @@ export function Minting({ throw new Error('Transaction failed.') } const tokenIds = await waitForTokenIds() - communication?.emitTransactionInfo({ - hash: mint!.transactionHash!, - lock: lock?.address, - tokenIds: tokenIds?.length ? tokenIds : [], - metadata, - }) - communication?.emitUserInfo({ - address: account, - signedMessage: messageToSign?.signature, - }) - communication?.emitMetadata(metadata) + communication?.emitTransactionInfo( + { + hash: mint!.transactionHash!, + lock: lock?.address, + tokenIds: tokenIds?.length ? tokenIds : [], + metadata, + }, + paywallConfig + ) + communication?.emitUserInfo( + { + address: account, + signedMessage: messageToSign?.signature, + }, + paywallConfig + ) + communication?.emitMetadata(metadata, paywallConfig) checkoutService.send({ type: 'CONFIRM_MINT', status: 'FINISHED', diff --git a/unlock-app/src/components/interface/checkout/main/Returning.tsx b/unlock-app/src/components/interface/checkout/main/Returning.tsx index e33c409c301..10d6708dcf2 100644 --- a/unlock-app/src/components/interface/checkout/main/Returning.tsx +++ b/unlock-app/src/components/interface/checkout/main/Returning.tsx @@ -55,11 +55,14 @@ export function Returning({ checkoutService, onClose, communication }: Props) { address: account!, }) setHasMessageToSign(false) - communication?.emitUserInfo({ - address: account, - message: paywallConfig.messageToSign, - signedMessage: signature, - }) + communication?.emitUserInfo( + { + address: account, + message: paywallConfig.messageToSign, + signedMessage: signature, + }, + paywallConfig + ) } catch (error) { if (error instanceof Error) { ToastHelper.error(error.message) diff --git a/unlock-app/src/components/interface/checkout/main/checkoutHookUtils.ts b/unlock-app/src/components/interface/checkout/main/checkoutHookUtils.ts index a35d4145e4f..3110e594b30 100644 --- a/unlock-app/src/components/interface/checkout/main/checkoutHookUtils.ts +++ b/unlock-app/src/components/interface/checkout/main/checkoutHookUtils.ts @@ -2,6 +2,7 @@ import { networks } from '@unlock-protocol/networks' import { HookType } from '@unlock-protocol/types' import { CheckoutHookType } from './checkoutMachine' import { PaywallConfigType } from '@unlock-protocol/core' +import { locksmith } from '~/config/locksmith' const HookIdMapping: Partial> = { PASSWORD: 'password', @@ -71,3 +72,21 @@ export const getOnPurchaseHookTypeFromPaywallConfig = ( return 'promocode' } } + +let prevBody: string | null = null +export const postToWebhook = async (body: any, config: any, event: string) => { + const url = config?.hooks && config.hooks[event] + if (!url) return + + if (JSON.stringify(body) === prevBody) { + return + } + prevBody = JSON.stringify(body) + + try { + const checkoutId = new URL(window.location.href).searchParams.get('id') + await locksmith.addCheckoutHookJob(checkoutId!, { data: body, event }) + } catch (error) { + console.error('job not added') + } +} diff --git a/unlock-app/src/components/interface/checkout/main/index.tsx b/unlock-app/src/components/interface/checkout/main/index.tsx index c44190f1717..9849798c536 100644 --- a/unlock-app/src/components/interface/checkout/main/index.tsx +++ b/unlock-app/src/components/interface/checkout/main/index.tsx @@ -75,8 +75,8 @@ export function Checkout({ useEffect(() => { const user = account ? { address: account } : {} - if (isInIframe() && communication) { - communication.emitUserInfo(user) + if (communication) { + communication.emitUserInfo(user, paywallConfig) } }, [account, communication]) diff --git a/unlock-app/src/components/interface/layouts/index/NotificationsMenu.tsx b/unlock-app/src/components/interface/layouts/index/NotificationsMenu.tsx index 761016b3fc7..56eb2602709 100644 --- a/unlock-app/src/components/interface/layouts/index/NotificationsMenu.tsx +++ b/unlock-app/src/components/interface/layouts/index/NotificationsMenu.tsx @@ -7,6 +7,9 @@ import { useAuthenticate } from '~/hooks/useAuthenticate' import { usePathname } from 'next/navigation' import { Modal } from '@unlock-protocol/ui' import { LoginModal } from '@privy-io/react-auth' +import { Hooks } from '../../HooksNotifications' +import { locksmith } from '~/config/locksmith' +import { useQuery } from '@tanstack/react-query' interface NotificationAction { label: string @@ -26,6 +29,16 @@ export function NotificationsMenu() { const { account, email } = useAuthenticate() const pathname = usePathname() + const { + isLoading, + error, + data: hooks, + refetch, + } = useQuery({ + queryKey: ['checkoutHookJobs'], + queryFn: () => locksmith.getCheckoutHookJobs(), + }) + if (!account) { return null } @@ -46,6 +59,14 @@ export function NotificationsMenu() { }) } + if (!isLoading && !error && hooks?.data?.length && hooks?.data?.length > 0) { + notifications.push({ + id: '2', + content: , + timestamp: new Date(), + }) + } + return ( <> diff --git a/unlock-app/src/hooks/useCheckoutCommunication.ts b/unlock-app/src/hooks/useCheckoutCommunication.ts index dd65169d754..d4f92819bb4 100644 --- a/unlock-app/src/hooks/useCheckoutCommunication.ts +++ b/unlock-app/src/hooks/useCheckoutCommunication.ts @@ -4,6 +4,7 @@ import { PaywallConfigType } from '@unlock-protocol/core' import { OAuthConfig } from '~/unlockTypes' import { useProvider } from './useProvider' import { isInIframe } from '~/utils/iframe' +import { postToWebhook } from '~/components/interface/checkout/main/checkoutHookUtils' export interface UserInfo { address?: string @@ -218,25 +219,28 @@ export const useCheckoutCommunication = () => { } }, [provider, incomingBuffer, handleMethodCallEvent]) - const emitUserInfo = (info: UserInfo) => { + const emitUserInfo = (info: UserInfo, paywallConfig?: any) => { // if user already emitted, avoid re-emitting if (info.address === user && !info.signedMessage) { return } setUser(info.address) pushOrEmit(CheckoutEvents.userInfo, info) + postToWebhook(info, paywallConfig, 'authenticated') } - const emitMetadata = (metadata: any) => { + const emitMetadata = (metadata: any, paywallConfig?: any) => { pushOrEmit(CheckoutEvents.metadata, metadata) + postToWebhook(metadata, paywallConfig, 'metadata') } const emitCloseModal = () => { pushOrEmit(CheckoutEvents.closeModal) } - const emitTransactionInfo = (info: TransactionInfo) => { + const emitTransactionInfo = (info: TransactionInfo, paywallConfig?: any) => { pushOrEmit(CheckoutEvents.transactionInfo, info) + postToWebhook(info, paywallConfig, 'transactionSent') } const emitMethodCall = (call: MethodCall) => {