如何正确设置 Webhook 端点 API 以使用 Firebase Functions 处理 RevenueCat 事件?

How do I properly set up a webhook endpoint API to handle RevenueCat events with Firebase Functions?

我在直接从 RevenueCat's documentation 设置 Webhook 端点 API 时遇到问题。

我的代码与文档中的代码几乎一模一样,所以我不知道为什么会触发此错误,而且我对此类问题的经验不足以解决此问题。这是我收到的错误:

(parameter) res: functions.Response<any>

Argument of type '(req: Request, res: Response<any>) => Response<any> | Promise<void | Response<any>>' is not assignable to parameter of type '(req: Request, resp: Response<any>) => void | Promise<void>'.
  Type 'Response<any> | Promise<void | Response<any>>' is not assignable to type 'void | Promise<void>'.
    Type 'Response<any>' is not assignable to type 'void | Promise<void>'.
      Type 'Response<any>' is missing the following properties from type 'Promise<void>': then, catch, finally, [Symbol.toStringTag]ts(2345)

老实说,我不确定它要求我更改什么。有任何想法吗? 这是我的代码:

import * as functions from 'firebase-functions'
import { PubSub } from '@google-cloud/pubsub'

const pubsubClient = new PubSub({projectId: '<PROJ_ID>'})

function isAuthorized(req: functions.https.Request) { 
    // Check authorization header
    if (!req.headers.authorization || !req.headers.authorization.startsWith('Bearer ')) { 
        return false
    }

    const authToken = req.headers.authorization.split('Bearer ')[1]
    if (authToken !== '<MY_AUTH_TOKEN>') { 
        return false
    }

    return true
}

// Respond to incoming message
export const revenueCatApi = functions.https.onRequest((req, res) => { // *** ERROR DETECTED HERE

    // Only allow POST request
    if (req.method !== 'POST') { 
        return res.status(403).send('Forbidden')
    }

    // Make sure the auth key matches what we set in the Revenue Cat dashboard
    if (!isAuthorized(req)) { 
        return res.status(401).send('Unauthorized')
    }

    const rc = req.body as RCEvent
    var topic: RCTopic = ''

    switch (rc.event.type) { 
        case 'INITIAL_PURCHASE':
            topic = 'rc-initial-purchase'
            break
        case 'NON_RENEWING_PURCHASE':
            topic = 'rc-non-renewing-purchase'
            break
        case 'RENEWAL':
            topic = 'rc-renewal'
            break
        case 'PRODUCT_CHANGE':
            topic = 'rc-product-change'
            break
        case 'CANCELLATION':
            topic = 'rc-cancellation'
            break
        case 'BILLING_ISSUE':
            topic = 'rc-billing-issue'
            break
        case 'SUBSCRIBER_ALIAS':
            topic = 'rc-subscriber-alias'
            break
        default:
            console.log('Unhandled event type: ', rc.event.type)
            return res.sendStatus(200)
    }

    // Set the pub/sub data to the event body
    const dataBuffer = Buffer.from(JSON.stringify(rc))

    // Publishes a message
    return pubsubClient.topic(topic)
        .publish(dataBuffer)
        .then(() => res.sendStatus(200))
        .catch(err => { 
            console.error(err)
            res.sendStatus(500)
            return Promise.reject(err)
        })
})

exports.handleInitialPurchase = functions.pubsub
    .topic('rc-initial-purchase')
    .onPublish(async (message, context) => {
        ...
    })

/* Other pubsub functions below */

RCE事件:

interface RCEvent { 
    api_version: string
    event: { 
        aliases: string[]
        app_id: string
        app_user_id: string
        country_code: string
        currency: string
        entitlement_id: string
        entitlement_ids: string[]
        environment: string
        event_timestamp_ms: number
        expiration_at_ms: number
        id: string
        is_family_share: boolean
        offer_code?: string
        original_app_user_id: string
        original_transaction_id: string
        period_type: string
        presented_offering_id: string
        price: number
        price_in_purchased_currency: number
        product_id: string
        purchased_at_ms: number
        store: string
        subscriber_attributes: any
        takehome_percentage: number
        transaction_id: string
        type: string
    }
}

错误消息告诉您 TypeScript 已确定您的函数的签名是这样的:

(req: Request, res: Response<any>) => Response<any> | Promise<void | Response<any>>

这意味着它将请求和响应作为参数,并且可以 return Response<any>Promise<void | Response<any>> 之一。这与 return 仅 voidPromise<void>.

的要求不兼容

你的函数可能 return 三件事:

return res.status(403).send('Forbidden')

return res.status(401).send('Unauthorized')

return pubsubClient.topic(topic)

前两件事你并没有真正尝试 return。您只是想发送响应并提前结束。第三件事是承诺。

不要 return res.status(403).send('Forbidden') 的结果。如果您想提前终止函数而不做任何进一步的工作,只需 return null。

res.status(403).send('Forbidden')
return null

对您没有承诺等待的两个 return 执行此操作。这将使您的函数声明它只是 return 一个 promise 或 null,这与该函数的 TypeScript 要求兼容。