Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs(backend): add webhooks openapi spec #1210

Merged
merged 12 commits into from
Mar 22, 2023
Merged

Conversation

sabineschaller
Copy link
Member

@sabineschaller sabineschaller commented Mar 10, 2023

Changes proposed in this pull request

Context

Checklist

  • Related issues linked using fixes #number
  • Tests added/updated
  • Documentation added
  • Make sure that all checks pass

@github-actions github-actions bot added pkg: backend Changes in the backend package. type: source Changes business logic labels Mar 10, 2023
@sabineschaller
Copy link
Member Author

To review, go to https://editor-next.swagger.io/ and paste resolved schema (or resolve it yourself first if you don't trust me 😉)

openapi: 3.1.0
info:
  title: Rafiki Webhooks
  version: 1.0.0
webhooks:
  incomingPaymentCompleted:
    post:
      requestBody:
        description: Notify account servicing entity that an incoming payment was
          completed and funds should now be withdrawn.
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/incomingPayment"
      responses:
        "200":
          description: Return a 200 status to indicate that the data was received
            successfully
  incomingPaymentExpired:
    post:
      requestBody:
        description: Notify account servicing entity that an incoming payment has
          expired and funds should now be withdrawn.
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/incomingPayment"
      responses:
        "200":
          description: Return a 200 status to indicate that the data was received
            successfully
  outgoingPaymentCreated:
    post:
      requestBody:
        description: Notify account servicing entity that an outgoing payment was
          created and should now be funded.
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/outgoingPayment"
      responses:
        "200":
          description: Return a 200 status to indicate that the data was received
            successfully
  outgoingPaymentCompleted:
    post:
      requestBody:
        description: Notify account servicing entity that an outgoing payment completed.
          Remaining funds should be withdrawn.
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/outgoingPayment"
      responses:
        "200":
          description: Return a 200 status to indicate that the data was received
            successfully
  outgoingPaymentFailed:
    post:
      requestBody:
        description: Notify account servicing entity that an outgoing payment failed and
          won't be retried. Funds should be withdrawn.
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/outgoingPayment"
      responses:
        "200":
          description: Return a 200 status to indicate that the data was received
            successfully
  webMonetization:
    post:
      requestBody:
        description: Notify account servicing entity that a Web Monetization payment was
          received and funds should be withdrawn.
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/webMonetization"
      responses:
        "200":
          description: Return a 200 status to indicate that the data was received
            successfully
components:
  schemas:
    incomingPayment:
      required:
        - id
        - type
        - attempts
        - processAt
        - data
      properties:
        id:
          type: string
          format: uuid
        type:
          type: string
          enum:
            - incoming_payment.completed
            - incoming_payment.expired
        attempts:
          type: integer
        processAt:
          type: string
          format: date-time
        data:
          type: object
          required:
            - id
            - paymentPointerId
            - createdAt
            - updatedAt
            - expiresAt
            - receivedAmount
            - completed
          properties:
            id:
              type: string
              format: uuid
            paymentPointerId:
              type: string
              format: uuid
            completed:
              type: boolean
            incomingAmount:
              title: amount
              type: object
              description: All amounts are maxima, i.e. multiple payments can be created under
                a grant as long as the total amounts of these payments do not
                exceed the maximum amount per interval as specified in the
                grant.
              properties:
                value:
                  type: string
                  format: uint64
                  description: The value is an unsigned 64-bit integer amount, represented as a
                    string.
                assetCode:
                  title: Asset code
                  type: string
                  description: The assetCode is a code that indicates the underlying asset. This
                    SHOULD be an ISO4217 currency code.
                assetScale:
                  title: Asset scale
                  type: integer
                  minimum: 0
                  maximum: 255
                  description: The scale of amounts denoted in the corresponding asset code.
              required:
                - value
                - assetCode
                - assetScale
            receivedAmount:
              $ref: "#/components/schemas/incomingPayment/properties/data/properties/incoming\
                Amount"
            description:
              type: string
            externalRef:
              type: string
            createdAt:
              type: string
              format: date-time
            updatedAt:
              type: string
              format: date-time
            expiresAt:
              type: string
              format: date-time
        statusCode:
          type: integer
        withdrawal:
          $ref: "#/components/schemas/withdrawal"
    outgoingPayment:
      required:
        - id
        - type
        - attempts
        - processAt
        - data
      properties:
        id:
          type: string
          format: uuid
        type:
          type: string
          enum:
            - outgoing_payment.created
            - outgoing_payment.completed
            - outgoing_payment.failed
        attempts:
          type: integer
        processAt:
          type: string
          format: date-time
        data:
          type: object
          required:
            - id
            - paymentPointerId
            - createdAt
            - updatedAt
            - expiresAt
            - sendAmount
            - receiveAmount
            - state
            - stateAttempts
            - balance
          properties:
            id:
              type: string
              format: uuid
            paymentPointerId:
              type: string
              format: uuid
            state:
              type: string
              enum:
                - FUNDING
                - SENDING
                - FAILED
                - COMPLETED
            sendAmount:
              $ref: "#/components/schemas/incomingPayment/properties/data/properties/incoming\
                Amount"
            sentAmount:
              $ref: "#/components/schemas/incomingPayment/properties/data/properties/incoming\
                Amount"
            description:
              type: string
            externalRef:
              type: string
            createdAt:
              type: string
              format: date-time
            updatedAt:
              type: string
              format: date-time
            expiresAt:
              type: string
              format: date-time
            error:
              type: string
            stateAttempts:
              type: integer
            balance:
              type: string
              format: uint64
            peerId:
              type: string
              format: uuid
        statusCode:
          type: integer
        withdrawal:
          $ref: "#/components/schemas/withdrawal"
    webMonetization:
      required:
        - id
        - type
        - attempts
        - processAt
        - data
      properties:
        id:
          type: string
          format: uuid
        type:
          type: string
          enum:
            - payment_pointer.web_monetization
        attempts:
          type: integer
        processAt:
          type: string
          format: date-time
        data:
          type: object
          required:
            - paymentPointer
          properties:
            paymentPointer:
              type: object
              required:
                - id
                - createdAt
                - received
              properties:
                id:
                  type: string
                  format: uuid
                createdAt:
                  type: string
                  format: date-time
                received:
                  type: string
                  format: uint64
        statusCode:
          type: integer
        withdrawal:
          $ref: "#/components/schemas/withdrawal"
    withdrawal:
      type: object
      required:
        - accountId: string
        - assetId: string
        - amount: bigint
      properties:
        accountId:
          type: string
          format: uuid
        assetId:
          type: string
          format: uuid
        amount:
          type: integer

@sabineschaller sabineschaller marked this pull request as ready for review March 10, 2023 12:59
@sabineschaller sabineschaller changed the title docs: add webhooks openapi spec docs(backend): add webhooks openapi spec Mar 10, 2023
Copy link
Contributor

@wilsonianb wilsonianb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we planning on incorporating this in the code?
I'm thinking at least add .toSatisfyApiSpec checks to webhook tests.
Maybe even get the MAP using our openapi package to validate requests.

@github-actions github-actions bot added the type: ci Changes to the CI label Mar 13, 2023
@sabineschaller
Copy link
Member Author

Are we planning on incorporating this in the code? I'm thinking at least add .toSatisfyApiSpec checks to webhook tests.

Which tests are you referring to exactly? packages/backend/src/webhook/service.test.ts?

Maybe even get the MAP using our openapi package to validate requests.

I can create an issue for that.

@wilsonianb
Copy link
Contributor

Which tests are you referring to exactly? packages/backend/src/webhook/service.test.ts?

Yeah, the webhook tests that I forgot we're skipping...

describe.skip('processNext', (): void => {
function mockWebhookServer(status = 200): nock.Scope {
return nock(webhookUrl.origin)
.post(webhookUrl.pathname, function (this: Definition, body) {
assert.ok(this.headers)
const signature = this.headers['rafiki-signature']
expect(
generateWebhookSignature(
body,
WEBHOOK_SECRET,
Config.signatureVersion
)
).toEqual(signature)
expect(body).toMatchObject({
id: event.id,
type: event.type,
data: event.data
})
return true
})
.reply(status)
}

format: date-time
statusCode:
type: integer
withdrawal:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we necessarily need withdrawal? it's not being used in the MAP webhooks handler, and looking at the webhook publishers, the only information that isn't already on the data is the assetId: something that we could just include in the data object as well IMO

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's only used by the outgoing payment event.
@wilsonianb can you explain its use?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

withdrawal isn't included in the request, just id, type, and data

const body = {
id: event.id,
type: event.type,
data: event.data
}
if (deps.config.signatureSecret) {
requestHeaders['Rafiki-Signature'] = generateWebhookSignature(
body,
deps.config.signatureSecret,
deps.config.signatureVersion
)
}
await axios.post(deps.config.webhookUrl, body, {
timeout: deps.config.webhookTimeout,
headers: requestHeaders,
validateStatus: (status) => status === 200
})

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was looking at this: https://github.com/interledger/rafiki/blob/main/packages/backend/src/webhook/model.ts#L8

Is it just id, type, and data for all of them?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, I think the webhook service code I linked 👆 is the only place we're sending webhook requests

Copy link
Member Author

@sabineschaller sabineschaller Mar 20, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about here? I think we have two different sendWebhookEvents (which we should clean up)

const handleCompleted = async (
deps: ServiceDependencies,
payment: OutgoingPayment
): Promise<void> => {
await payment.$query(deps.knex).patch({
state: OutgoingPaymentState.Completed
})
await sendWebhookEvent(deps, payment, PaymentEventType.PaymentCompleted)
}
export const sendWebhookEvent = async (
deps: ServiceDependencies,
payment: OutgoingPayment,
type: PaymentEventType
): Promise<void> => {
// Tigerbeetle accounts are only created as the OutgoingPayment is funded.
// So default the amountSent and balance to 0 for outgoing payments still in the funding state
const amountSent =
payment.state === OutgoingPaymentState.Funding
? BigInt(0)
: await deps.accountingService.getTotalSent(payment.id)
const balance =
payment.state === OutgoingPaymentState.Funding
? BigInt(0)
: await deps.accountingService.getBalance(payment.id)
if (amountSent === undefined || balance === undefined) {
throw LifecycleError.MissingBalance
}
const withdrawal = balance
? {
accountId: payment.id,
assetId: payment.assetId,
amount: balance
}
: undefined
await PaymentEvent.query(deps.knex).insertAndFetch({
type,
data: payment.toData({ amountSent, balance }),
withdrawal
})
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one ^ just adds it to the DB to prepare for sending. It looks the event.withdrawal doesn't gets added to the webhook POST request (the one that @wilsonianb commented), since body just has id type data.

Maybe we can even just remote withdrawal from the code completely on a follow-up PR?

@sabineschaller
Copy link
Member Author

Yeah, the webhook tests that I forgot we're skipping...

@wilsonianb why are we skipping them? They pass, but I see the warning

Jest did not exit one second after the test run has completed.

'This usually means that there are asynchronous operations that weren't stopped in your tests. Consider running Jest with `--detectOpenHandles` to troubleshoot this issue.

@wilsonianb
Copy link
Contributor

Yeah, the webhook tests that I forgot we're skipping...

@wilsonianb why are we skipping them? They pass, but I see the warning

Jest did not exit one second after the test run has completed.

'This usually means that there are asynchronous operations that weren't stopped in your tests. Consider running Jest with `--detectOpenHandles` to troubleshoot this issue.

We were updating dependencies 😅

mkurapov
mkurapov previously approved these changes Mar 20, 2023
- receiveAmount
- state
- stateAttempts
- balance
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- balance
- balance
- receiver

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch!

@sabineschaller sabineschaller merged commit 09c5d71 into main Mar 22, 2023
@sabineschaller sabineschaller deleted the s2-docs-webhook-spec branch March 22, 2023 12:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
pkg: backend Changes in the backend package. type: ci Changes to the CI type: source Changes business logic
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Document Webhooks
3 participants