From bd58699181303363bfcda38cce9484af93f49984 Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Mon, 9 Mar 2026 06:30:33 +0200 Subject: [PATCH] feat(sdk-ts): payment links sdk ts utils --- .../BillPayments/BillPayments.controller.ts | 3 +- .../CreditNotes/CreditNotes.controller.ts | 7 +- .../dtos/CreditNoteStateResponse.dto.ts | 9 + .../InventoryCost/InventoryCost.controller.ts | 10 +- .../dtos/GetInventoryItemsCostResponse.dto.ts | 23 +++ .../PaymentLinks/PaymentLinks.controller.ts | 25 +-- ...CreateStripeCheckoutSessionResponse.dto.ts | 21 +++ .../dtos/GetInvoicePaymentLinkResponse.dto.ts | 171 ++++++++++++++++++ .../StripePayment/CreateStripeAccountLink.ts | 8 + .../StripePayment/StripePayment.controller.ts | 91 ++++++++-- .../StripePayment/StripePaymentApplication.ts | 8 + .../dtos/CreateStripeAccountLinkBody.dto.ts | 11 ++ .../CreateStripeAccountLinkResponse.dto.ts | 10 + .../dtos/CreateStripeAccountResponse.dto.ts | 9 + .../CreateStripeAccountSessionBody.dto.ts | 10 + .../CreateStripeAccountSessionResponse.dto.ts | 9 + .../dtos/ExchangeStripeOAuthBody.dto.ts | 11 ++ .../dtos/GetStripeConnectLinkResponse.dto.ts | 9 + .../dtos/StripeAccountLinkResponse.dto.ts | 27 +++ shared/sdk-ts/src/bank-rules.ts | 13 +- shared/sdk-ts/src/credit-notes.ts | 12 +- shared/sdk-ts/src/customers.ts | 12 ++ shared/sdk-ts/src/import.ts | 70 +++++++ shared/sdk-ts/src/index.ts | 7 + shared/sdk-ts/src/inventory-cost.ts | 34 ++++ shared/sdk-ts/src/landed-cost.ts | 32 +++- shared/sdk-ts/src/organization.ts | 22 +++ shared/sdk-ts/src/payment-links.ts | 72 ++++++++ shared/sdk-ts/src/payment-mades.ts | 18 ++ shared/sdk-ts/src/payment-services.ts | 120 ++++++++++++ shared/sdk-ts/src/plaid.ts | 40 ++++ shared/sdk-ts/src/schema.ts | 74 ++++++++ shared/sdk-ts/src/stripe-integration.ts | 102 +++++++++++ shared/sdk-ts/src/subscription.ts | 22 ++- shared/sdk-ts/src/vendor-credits.ts | 2 +- shared/sdk-ts/src/vendors.ts | 12 ++ 36 files changed, 1088 insertions(+), 48 deletions(-) create mode 100644 packages/server/src/modules/CreditNotes/dtos/CreditNoteStateResponse.dto.ts create mode 100644 packages/server/src/modules/InventoryCost/dtos/GetInventoryItemsCostResponse.dto.ts create mode 100644 packages/server/src/modules/PaymentLinks/dtos/CreateStripeCheckoutSessionResponse.dto.ts create mode 100644 packages/server/src/modules/PaymentLinks/dtos/GetInvoicePaymentLinkResponse.dto.ts create mode 100644 packages/server/src/modules/StripePayment/dtos/CreateStripeAccountLinkBody.dto.ts create mode 100644 packages/server/src/modules/StripePayment/dtos/CreateStripeAccountLinkResponse.dto.ts create mode 100644 packages/server/src/modules/StripePayment/dtos/CreateStripeAccountResponse.dto.ts create mode 100644 packages/server/src/modules/StripePayment/dtos/CreateStripeAccountSessionBody.dto.ts create mode 100644 packages/server/src/modules/StripePayment/dtos/CreateStripeAccountSessionResponse.dto.ts create mode 100644 packages/server/src/modules/StripePayment/dtos/ExchangeStripeOAuthBody.dto.ts create mode 100644 packages/server/src/modules/StripePayment/dtos/GetStripeConnectLinkResponse.dto.ts create mode 100644 packages/server/src/modules/StripePayment/dtos/StripeAccountLinkResponse.dto.ts create mode 100644 shared/sdk-ts/src/import.ts create mode 100644 shared/sdk-ts/src/inventory-cost.ts create mode 100644 shared/sdk-ts/src/payment-links.ts create mode 100644 shared/sdk-ts/src/payment-services.ts create mode 100644 shared/sdk-ts/src/plaid.ts create mode 100644 shared/sdk-ts/src/stripe-integration.ts diff --git a/packages/server/src/modules/BillPayments/BillPayments.controller.ts b/packages/server/src/modules/BillPayments/BillPayments.controller.ts index 80a442f3c..4b0aba2c2 100644 --- a/packages/server/src/modules/BillPayments/BillPayments.controller.ts +++ b/packages/server/src/modules/BillPayments/BillPayments.controller.ts @@ -14,6 +14,7 @@ import { ApiExtraModels, ApiOperation, ApiParam, + ApiQuery, ApiResponse, ApiTags, getSchemaPath, @@ -92,7 +93,7 @@ export class BillPaymentsController { summary: 'Retrieves the payable entries of the new page once vendor be selected.', }) - @ApiParam({ + @ApiQuery({ name: 'vendorId', required: true, type: Number, diff --git a/packages/server/src/modules/CreditNotes/CreditNotes.controller.ts b/packages/server/src/modules/CreditNotes/CreditNotes.controller.ts index 649a714da..d9b843fc1 100644 --- a/packages/server/src/modules/CreditNotes/CreditNotes.controller.ts +++ b/packages/server/src/modules/CreditNotes/CreditNotes.controller.ts @@ -24,6 +24,7 @@ import { CreditNoteApplication } from './CreditNoteApplication.service'; import { ICreditNotesQueryDTO } from './types/CreditNotes.types'; import { CreateCreditNoteDto, EditCreditNoteDto } from './dtos/CreditNote.dto'; import { CreditNoteResponseDto } from './dtos/CreditNoteResponse.dto'; +import { CreditNoteStateResponseDto } from './dtos/CreditNoteStateResponse.dto'; import { PaginatedResponseDto } from '@/common/dtos/PaginatedResults.dto'; import { ApiCommonHeaders } from '@/common/decorators/ApiCommonHeaders'; import { @@ -62,7 +63,11 @@ export class CreditNotesController { @Get('state') @RequirePermission(CreditNoteAction.View, AbilitySubject.CreditNote) @ApiOperation({ summary: 'Get credit note state' }) - @ApiResponse({ status: 200, description: 'Returns the credit note state' }) + @ApiResponse({ + status: 200, + description: 'Returns the credit note state', + type: CreditNoteStateResponseDto, + }) getCreditNoteState() { return this.creditNoteApplication.getCreditNoteState(); } diff --git a/packages/server/src/modules/CreditNotes/dtos/CreditNoteStateResponse.dto.ts b/packages/server/src/modules/CreditNotes/dtos/CreditNoteStateResponse.dto.ts new file mode 100644 index 000000000..36fdd8ea1 --- /dev/null +++ b/packages/server/src/modules/CreditNotes/dtos/CreditNoteStateResponse.dto.ts @@ -0,0 +1,9 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class CreditNoteStateResponseDto { + @ApiProperty({ + description: 'Default PDF template ID for credit notes', + example: 1, + }) + defaultTemplateId: number; +} diff --git a/packages/server/src/modules/InventoryCost/InventoryCost.controller.ts b/packages/server/src/modules/InventoryCost/InventoryCost.controller.ts index 15b456b0b..4b94a1f68 100644 --- a/packages/server/src/modules/InventoryCost/InventoryCost.controller.ts +++ b/packages/server/src/modules/InventoryCost/InventoryCost.controller.ts @@ -1,7 +1,8 @@ import { Controller, Get, Query } from '@nestjs/common'; import { GetItemsInventoryValuationListService } from './queries/GetItemsInventoryValuationList.service'; import { GetInventoyItemsCostQueryDto } from './dtos/GetInventoryItemsCostQuery.dto'; -import { ApiOperation, ApiTags } from '@nestjs/swagger'; +import { GetInventoryItemsCostResponseDto } from './dtos/GetInventoryItemsCostResponse.dto'; +import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; import { ApiCommonHeaders } from '@/common/decorators/ApiCommonHeaders'; @Controller('inventory-cost') @@ -14,9 +15,14 @@ export class InventoryCostController { @Get('items') @ApiOperation({ summary: 'Get items inventory valuation list' }) + @ApiResponse({ + status: 200, + description: 'Items inventory cost list', + type: GetInventoryItemsCostResponseDto, + }) async getItemsCost( @Query() itemsCostsQueryDto: GetInventoyItemsCostQueryDto, - ) { + ): Promise { const costs = await this.inventoryItemCost.getItemsInventoryValuationList( itemsCostsQueryDto.itemsIds, itemsCostsQueryDto.date, diff --git a/packages/server/src/modules/InventoryCost/dtos/GetInventoryItemsCostResponse.dto.ts b/packages/server/src/modules/InventoryCost/dtos/GetInventoryItemsCostResponse.dto.ts new file mode 100644 index 000000000..9bc498ce0 --- /dev/null +++ b/packages/server/src/modules/InventoryCost/dtos/GetInventoryItemsCostResponse.dto.ts @@ -0,0 +1,23 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class InventoryItemCostDto { + @ApiProperty({ description: 'Item ID' }) + itemId: number; + + @ApiProperty({ description: 'Valuation' }) + valuation: number; + + @ApiProperty({ description: 'Quantity' }) + quantity: number; + + @ApiProperty({ description: 'Average cost' }) + average: number; +} + +export class GetInventoryItemsCostResponseDto { + @ApiProperty({ + type: [InventoryItemCostDto], + description: 'List of item costs', + }) + costs: InventoryItemCostDto[]; +} diff --git a/packages/server/src/modules/PaymentLinks/PaymentLinks.controller.ts b/packages/server/src/modules/PaymentLinks/PaymentLinks.controller.ts index 6be5bfb0b..4acf9d220 100644 --- a/packages/server/src/modules/PaymentLinks/PaymentLinks.controller.ts +++ b/packages/server/src/modules/PaymentLinks/PaymentLinks.controller.ts @@ -3,6 +3,10 @@ import { Controller, Get, Param, Post, Res } from '@nestjs/common'; import { PaymentLinksApplication } from './PaymentLinksApplication'; import { ApiOperation, ApiParam, ApiResponse, ApiTags } from '@nestjs/swagger'; import { ApiCommonHeaders } from '@/common/decorators/ApiCommonHeaders'; +import { + GetInvoicePaymentLinkResponseWrapperDto, +} from './dtos/GetInvoicePaymentLinkResponse.dto'; +import { CreateStripeCheckoutSessionResponseDto } from './dtos/CreateStripeCheckoutSessionResponse.dto'; @Controller('payment-links') @ApiTags('Payment Links') @@ -24,12 +28,7 @@ export class PaymentLinksController { @ApiResponse({ status: 200, description: 'Successfully retrieved payment link metadata', - schema: { - type: 'object', - properties: { - data: { type: 'object', description: 'Payment link metadata' }, - }, - }, + type: GetInvoicePaymentLinkResponseWrapperDto, }) @ApiResponse({ status: 404, description: 'Payment link not found' }) public async getPaymentLinkPublicMeta( @@ -55,19 +54,7 @@ export class PaymentLinksController { @ApiResponse({ status: 200, description: 'Successfully created Stripe checkout session', - schema: { - type: 'object', - properties: { - id: { - type: 'string', - description: 'Stripe checkout session ID', - }, - url: { - type: 'string', - description: 'Stripe checkout session URL', - }, - }, - }, + type: CreateStripeCheckoutSessionResponseDto, }) @ApiResponse({ status: 404, description: 'Payment link not found' }) public async createInvoicePaymentLinkCheckoutSession( diff --git a/packages/server/src/modules/PaymentLinks/dtos/CreateStripeCheckoutSessionResponse.dto.ts b/packages/server/src/modules/PaymentLinks/dtos/CreateStripeCheckoutSessionResponse.dto.ts new file mode 100644 index 000000000..16a2b316a --- /dev/null +++ b/packages/server/src/modules/PaymentLinks/dtos/CreateStripeCheckoutSessionResponse.dto.ts @@ -0,0 +1,21 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class CreateStripeCheckoutSessionResponseDto { + @ApiProperty({ + description: 'Stripe checkout session ID', + example: 'cs_test_xxx', + }) + sessionId: string; + + @ApiProperty({ + description: 'Stripe publishable key for the client', + example: 'pk_test_xxx', + }) + publishableKey: string; + + @ApiProperty({ + description: 'URL to redirect the customer to complete checkout', + example: 'https://checkout.stripe.com/c/pay/cs_test_xxx', + }) + redirectTo: string; +} diff --git a/packages/server/src/modules/PaymentLinks/dtos/GetInvoicePaymentLinkResponse.dto.ts b/packages/server/src/modules/PaymentLinks/dtos/GetInvoicePaymentLinkResponse.dto.ts new file mode 100644 index 000000000..37328a239 --- /dev/null +++ b/packages/server/src/modules/PaymentLinks/dtos/GetInvoicePaymentLinkResponse.dto.ts @@ -0,0 +1,171 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class PaymentLinkAddressDto { + @ApiProperty({ description: 'Address line 1' }) + address_1: string; + + @ApiProperty({ description: 'Address line 2' }) + address_2: string; + + @ApiProperty({ description: 'Postal code' }) + postal_code: string; + + @ApiProperty({ description: 'City' }) + city: string; + + @ApiProperty({ description: 'State or province' }) + state_province: string; + + @ApiProperty({ description: 'Phone number' }) + phone: string; +} + +export class PaymentLinkOrganizationDto { + @ApiProperty({ type: 'object', description: 'Organization address' }) + address: Record; + + @ApiProperty({ description: 'Organization name' }) + name: string; + + @ApiProperty({ description: 'Primary brand color' }) + primaryColor: string; + + @ApiProperty({ description: 'Logo URI' }) + logoUri: string; + + @ApiProperty({ description: 'Formatted address text' }) + addressTextFormatted: string; +} + +export class PaymentLinkEntryDto { + @ApiProperty({ description: 'Line item description' }) + description: string; + + @ApiProperty({ description: 'Item name' }) + itemName: string; + + @ApiProperty({ description: 'Quantity' }) + quantity: number; + + @ApiProperty({ description: 'Formatted quantity' }) + quantityFormatted: string; + + @ApiProperty({ description: 'Unit rate' }) + rate: number; + + @ApiProperty({ description: 'Formatted rate' }) + rateFormatted: string; + + @ApiProperty({ description: 'Line total' }) + total: number; + + @ApiProperty({ description: 'Formatted total' }) + totalFormatted: string; +} + +export class PaymentLinkTaxEntryDto { + @ApiProperty({ description: 'Tax name' }) + name: string; + + @ApiProperty({ description: 'Tax rate amount' }) + taxRateAmount: number; + + @ApiProperty({ description: 'Formatted tax rate amount' }) + taxRateAmountFormatted: string; + + @ApiProperty({ description: 'Tax rate code' }) + taxRateCode: string; +} + +export class PaymentLinkBrandingTemplateDto { + @ApiProperty({ description: 'Company logo URI' }) + companyLogoUri: string; + + @ApiProperty({ description: 'Primary color' }) + primaryColor: string; + + @ApiProperty({ description: 'Secondary color', required: false }) + secondaryColor?: string; +} + +export class GetInvoicePaymentLinkResponseDto { + @ApiProperty({ description: 'Amount due' }) + dueAmount: number; + + @ApiProperty({ description: 'Formatted amount due' }) + dueAmountFormatted: string; + + @ApiProperty({ description: 'Due date' }) + dueDate: string; + + @ApiProperty({ description: 'Formatted due date' }) + dueDateFormatted: string; + + @ApiProperty({ description: 'Formatted invoice date' }) + invoiceDateFormatted: string; + + @ApiProperty({ description: 'Invoice number' }) + invoiceNo: string; + + @ApiProperty({ description: 'Payment amount' }) + paymentAmount: number; + + @ApiProperty({ description: 'Formatted payment amount' }) + paymentAmountFormatted: string; + + @ApiProperty({ description: 'Subtotal' }) + subtotal: number; + + @ApiProperty({ description: 'Formatted subtotal' }) + subtotalFormatted: string; + + @ApiProperty({ description: 'Formatted subtotal in local currency' }) + subtotalLocalFormatted: string; + + @ApiProperty({ description: 'Total amount' }) + total: number; + + @ApiProperty({ description: 'Formatted total' }) + totalFormatted: string; + + @ApiProperty({ description: 'Formatted total in local currency' }) + totalLocalFormatted: string; + + @ApiProperty({ description: 'Customer name' }) + customerName: string; + + @ApiProperty({ description: 'Invoice message' }) + invoiceMessage: string; + + @ApiProperty({ description: 'Terms and conditions' }) + termsConditions: string; + + @ApiProperty({ type: [PaymentLinkEntryDto], description: 'Invoice line entries' }) + entries: PaymentLinkEntryDto[]; + + @ApiProperty({ type: [PaymentLinkTaxEntryDto], description: 'Tax entries' }) + taxes: PaymentLinkTaxEntryDto[]; + + @ApiProperty({ type: PaymentLinkBrandingTemplateDto, description: 'Branding template' }) + brandingTemplate: PaymentLinkBrandingTemplateDto; + + @ApiProperty({ type: PaymentLinkOrganizationDto, description: 'Organization metadata' }) + organization: PaymentLinkOrganizationDto; + + @ApiProperty({ description: 'Whether Stripe is available as payment method' }) + hasStripePaymentMethod: boolean; + + @ApiProperty({ description: 'Whether invoice has receivable balance' }) + isReceivable: boolean; + + @ApiProperty({ description: 'Formatted customer address' }) + formattedCustomerAddress: string; +} + +export class GetInvoicePaymentLinkResponseWrapperDto { + @ApiProperty({ + type: GetInvoicePaymentLinkResponseDto, + description: 'Payment link invoice metadata', + }) + data: GetInvoicePaymentLinkResponseDto; +} diff --git a/packages/server/src/modules/StripePayment/CreateStripeAccountLink.ts b/packages/server/src/modules/StripePayment/CreateStripeAccountLink.ts index ddba0de52..f57677071 100644 --- a/packages/server/src/modules/StripePayment/CreateStripeAccountLink.ts +++ b/packages/server/src/modules/StripePayment/CreateStripeAccountLink.ts @@ -12,4 +12,12 @@ export class CreateStripeAccountLinkService { public createAccountLink(stripeAccountId: string) { return this.stripePaymentService.createAccountLink(stripeAccountId); } + + /** + * Creates a Stripe account session for the Connect embedded component. + * @param {string} accountId - Stripe Connect account ID. + */ + public createAccountSession(accountId: string) { + return this.stripePaymentService.createAccountSession(accountId); + } } diff --git a/packages/server/src/modules/StripePayment/StripePayment.controller.ts b/packages/server/src/modules/StripePayment/StripePayment.controller.ts index d2ccc1dc9..fea3c65ed 100644 --- a/packages/server/src/modules/StripePayment/StripePayment.controller.ts +++ b/packages/server/src/modules/StripePayment/StripePayment.controller.ts @@ -1,6 +1,13 @@ import { Body, Controller, Get, Injectable, Post } from '@nestjs/common'; import { StripePaymentApplication } from './StripePaymentApplication'; -import { ApiTags } from '@nestjs/swagger'; +import { ApiBody, ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; +import { GetStripeConnectLinkResponseDto } from './dtos/GetStripeConnectLinkResponse.dto'; +import { ExchangeStripeOAuthBodyDto } from './dtos/ExchangeStripeOAuthBody.dto'; +import { CreateStripeAccountLinkBodyDto } from './dtos/CreateStripeAccountLinkBody.dto'; +import { CreateStripeAccountLinkResponseDto } from './dtos/CreateStripeAccountLinkResponse.dto'; +import { CreateStripeAccountResponseDto } from './dtos/CreateStripeAccountResponse.dto'; +import { CreateStripeAccountSessionBodyDto } from './dtos/CreateStripeAccountSessionBody.dto'; +import { CreateStripeAccountSessionResponseDto } from './dtos/CreateStripeAccountSessionResponse.dto'; @Controller('/stripe') @ApiTags('stripe') @@ -9,9 +16,17 @@ export class StripeIntegrationController { /** * Retrieves Stripe OAuth2 connect link. - * @returns {Promise} */ @Get('/link') + @ApiOperation({ + summary: 'Get Stripe Connect link', + description: 'Retrieves the Stripe OAuth2 Connect authorization URL', + }) + @ApiResponse({ + status: 200, + description: 'Successfully retrieved Stripe Connect link', + type: GetStripeConnectLinkResponseDto, + }) public async getStripeConnectLink() { const authorizationUri = this.stripePaymentApp.getStripeConnectLink(); @@ -20,37 +35,85 @@ export class StripeIntegrationController { /** * Exchanges the given Stripe authorization code to Stripe user id and access token. - * @returns {Promise} */ @Post('/callback') - public async exchangeOAuth(@Body('code') code: string) { - await this.stripePaymentApp.exchangeStripeOAuthToken(code); + @ApiOperation({ + summary: 'Exchange Stripe OAuth code', + description: + 'Exchanges the Stripe authorization code for user id and access token', + }) + @ApiBody({ type: ExchangeStripeOAuthBodyDto }) + @ApiResponse({ + status: 201, + description: 'Successfully exchanged OAuth code', + }) + public async exchangeOAuth(@Body() body: ExchangeStripeOAuthBodyDto) { + await this.stripePaymentApp.exchangeStripeOAuthToken(body.code); return {}; } /** * Creates a new Stripe account. - * @returns {Promise} */ + @Post('/account') + @ApiOperation({ + summary: 'Create Stripe account', + description: 'Creates a new Stripe Connect account', + }) + @ApiResponse({ + status: 201, + description: 'Successfully created Stripe account', + type: CreateStripeAccountResponseDto, + }) public async createAccount() { const accountId = await this.stripePaymentApp.createStripeAccount(); - return { - accountId, - message: 'The Stripe account has been created successfully.', - }; + return { account_id: accountId }; } /** - * Creates a new Stripe account session. - * @returns {Promise} + * Creates a Stripe account session for the Connect embedded component. + */ + @Post('/account_session') + @ApiOperation({ + summary: 'Create Stripe account session', + description: + 'Creates an account session for the Stripe Connect embedded component', + }) + @ApiBody({ type: CreateStripeAccountSessionBodyDto }) + @ApiResponse({ + status: 201, + description: 'Successfully created account session', + type: CreateStripeAccountSessionResponseDto, + }) + public async createAccountSession( + @Body() body: CreateStripeAccountSessionBodyDto, + ) { + const clientSecret = await this.stripePaymentApp.createAccountSession( + body.account ?? '', + ); + return { client_secret: clientSecret }; + } + + /** + * Creates a new Stripe account link for onboarding. */ @Post('/account_link') + @ApiOperation({ + summary: 'Create Stripe account link', + description: 'Creates a Stripe Connect account link for onboarding', + }) + @ApiBody({ type: CreateStripeAccountLinkBodyDto }) + @ApiResponse({ + status: 201, + description: 'Successfully created account link', + type: CreateStripeAccountLinkResponseDto, + }) public async createAccountLink( - @Body('stripeAccountId') stripeAccountId: string, + @Body() body: CreateStripeAccountLinkBodyDto, ) { const clientSecret = - await this.stripePaymentApp.createAccountLink(stripeAccountId); + await this.stripePaymentApp.createAccountLink(body.stripeAccountId); return { clientSecret }; } diff --git a/packages/server/src/modules/StripePayment/StripePaymentApplication.ts b/packages/server/src/modules/StripePayment/StripePaymentApplication.ts index 62303dd39..3c588dcff 100644 --- a/packages/server/src/modules/StripePayment/StripePaymentApplication.ts +++ b/packages/server/src/modules/StripePayment/StripePaymentApplication.ts @@ -37,6 +37,14 @@ export class StripePaymentApplication { ); } + /** + * Creates a Stripe account session for the Connect embedded component. + * @param {string} accountId - Stripe Connect account ID. + */ + public createAccountSession(accountId: string) { + return this.createStripeAccountLinkService.createAccountSession(accountId); + } + /** * Retrieves Stripe OAuth2 connect link. * @returns {string} diff --git a/packages/server/src/modules/StripePayment/dtos/CreateStripeAccountLinkBody.dto.ts b/packages/server/src/modules/StripePayment/dtos/CreateStripeAccountLinkBody.dto.ts new file mode 100644 index 000000000..b34a52f92 --- /dev/null +++ b/packages/server/src/modules/StripePayment/dtos/CreateStripeAccountLinkBody.dto.ts @@ -0,0 +1,11 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsString } from 'class-validator'; + +export class CreateStripeAccountLinkBodyDto { + @ApiProperty({ + description: 'Stripe Connect account ID', + example: 'acct_xxx', + }) + @IsString() + stripeAccountId: string; +} diff --git a/packages/server/src/modules/StripePayment/dtos/CreateStripeAccountLinkResponse.dto.ts b/packages/server/src/modules/StripePayment/dtos/CreateStripeAccountLinkResponse.dto.ts new file mode 100644 index 000000000..dbaa2926e --- /dev/null +++ b/packages/server/src/modules/StripePayment/dtos/CreateStripeAccountLinkResponse.dto.ts @@ -0,0 +1,10 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { StripeAccountLinkResponseDto } from './StripeAccountLinkResponse.dto'; + +export class CreateStripeAccountLinkResponseDto { + @ApiProperty({ + type: StripeAccountLinkResponseDto, + description: 'Stripe AccountLink object for onboarding', + }) + clientSecret: StripeAccountLinkResponseDto; +} diff --git a/packages/server/src/modules/StripePayment/dtos/CreateStripeAccountResponse.dto.ts b/packages/server/src/modules/StripePayment/dtos/CreateStripeAccountResponse.dto.ts new file mode 100644 index 000000000..6fc60d371 --- /dev/null +++ b/packages/server/src/modules/StripePayment/dtos/CreateStripeAccountResponse.dto.ts @@ -0,0 +1,9 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class CreateStripeAccountResponseDto { + @ApiProperty({ + description: 'The Stripe Connect account ID', + example: 'acct_1234567890', + }) + account_id: string; +} diff --git a/packages/server/src/modules/StripePayment/dtos/CreateStripeAccountSessionBody.dto.ts b/packages/server/src/modules/StripePayment/dtos/CreateStripeAccountSessionBody.dto.ts new file mode 100644 index 000000000..03ba5bba4 --- /dev/null +++ b/packages/server/src/modules/StripePayment/dtos/CreateStripeAccountSessionBody.dto.ts @@ -0,0 +1,10 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class CreateStripeAccountSessionBodyDto { + @ApiProperty({ + description: 'Stripe Connect account ID to create a session for', + example: 'acct_1234567890', + required: false, + }) + account?: string; +} diff --git a/packages/server/src/modules/StripePayment/dtos/CreateStripeAccountSessionResponse.dto.ts b/packages/server/src/modules/StripePayment/dtos/CreateStripeAccountSessionResponse.dto.ts new file mode 100644 index 000000000..97a567b44 --- /dev/null +++ b/packages/server/src/modules/StripePayment/dtos/CreateStripeAccountSessionResponse.dto.ts @@ -0,0 +1,9 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class CreateStripeAccountSessionResponseDto { + @ApiProperty({ + description: 'Stripe Account Session client secret for the Connect embedded component', + example: 'acs_xxx_secret_xxx', + }) + client_secret: string; +} diff --git a/packages/server/src/modules/StripePayment/dtos/ExchangeStripeOAuthBody.dto.ts b/packages/server/src/modules/StripePayment/dtos/ExchangeStripeOAuthBody.dto.ts new file mode 100644 index 000000000..3402db667 --- /dev/null +++ b/packages/server/src/modules/StripePayment/dtos/ExchangeStripeOAuthBody.dto.ts @@ -0,0 +1,11 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsString } from 'class-validator'; + +export class ExchangeStripeOAuthBodyDto { + @ApiProperty({ + description: 'Authorization code returned by Stripe OAuth', + example: 'ac_xxx', + }) + @IsString() + code: string; +} diff --git a/packages/server/src/modules/StripePayment/dtos/GetStripeConnectLinkResponse.dto.ts b/packages/server/src/modules/StripePayment/dtos/GetStripeConnectLinkResponse.dto.ts new file mode 100644 index 000000000..2b19a5596 --- /dev/null +++ b/packages/server/src/modules/StripePayment/dtos/GetStripeConnectLinkResponse.dto.ts @@ -0,0 +1,9 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class GetStripeConnectLinkResponseDto { + @ApiProperty({ + description: 'Stripe OAuth2 Connect authorization URL', + example: 'https://connect.stripe.com/oauth/authorize?response_type=code&client_id=...', + }) + url: string; +} diff --git a/packages/server/src/modules/StripePayment/dtos/StripeAccountLinkResponse.dto.ts b/packages/server/src/modules/StripePayment/dtos/StripeAccountLinkResponse.dto.ts new file mode 100644 index 000000000..d7c3920cd --- /dev/null +++ b/packages/server/src/modules/StripePayment/dtos/StripeAccountLinkResponse.dto.ts @@ -0,0 +1,27 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class StripeAccountLinkResponseDto { + @ApiProperty({ + description: 'URL for the account onboarding flow', + example: 'https://connect.stripe.com/setup/xxx', + }) + url: string; + + @ApiProperty({ + description: 'Unix timestamp when the link was created', + example: 1234567890, + }) + created: number; + + @ApiProperty({ + description: 'Unix timestamp when the link expires', + example: 1234567890, + }) + expires_at: number; + + @ApiProperty({ + description: 'Stripe object type', + example: 'account_link', + }) + object: string; +} diff --git a/shared/sdk-ts/src/bank-rules.ts b/shared/sdk-ts/src/bank-rules.ts index 38b9370f0..4f7a1113d 100644 --- a/shared/sdk-ts/src/bank-rules.ts +++ b/shared/sdk-ts/src/bank-rules.ts @@ -276,10 +276,11 @@ export async function uncategorizeTransactionsBulk( const del = fetcher .path(BANK_RULES_ROUTES.CATEGORIZE_BULK) .method('delete') - .create(); - await (del as (params: { - query?: { uncategorizedTransactionIds: number[] }; - }) => Promise)({ - query: { uncategorizedTransactionIds }, - }); + .create( + { uncategorizedTransactionIds } as unknown as { + uncategorizedTransactionIds: true | 1; + }, + ); + // create() binds query; call with no args (params were passed to create()) + await (del as unknown as () => Promise)(); } diff --git a/shared/sdk-ts/src/credit-notes.ts b/shared/sdk-ts/src/credit-notes.ts index dd3297efd..4704b7293 100644 --- a/shared/sdk-ts/src/credit-notes.ts +++ b/shared/sdk-ts/src/credit-notes.ts @@ -44,9 +44,17 @@ export async function fetchCreditNote(fetcher: ApiFetcher, id: number): Promise< return data; } -export async function fetchCreditNoteState(fetcher: ApiFetcher): Promise { +/** Credit note state (default template etc.). Defined in controller DTO when not in schema. */ +export interface CreditNoteStateResponse { + defaultTemplateId: number; +} + +export async function fetchCreditNoteState( + fetcher: ApiFetcher +): Promise { const getState = fetcher.path(CREDIT_NOTES_ROUTES.STATE).method('get').create(); - await getState({}); + const { data } = await getState({}); + return (data ?? { defaultTemplateId: 0 }) as CreditNoteStateResponse; } export async function createCreditNote( diff --git a/shared/sdk-ts/src/customers.ts b/shared/sdk-ts/src/customers.ts index 394514c39..0415dc70a 100644 --- a/shared/sdk-ts/src/customers.ts +++ b/shared/sdk-ts/src/customers.ts @@ -17,6 +17,8 @@ export type EditCustomerBody = OpRequestBody>; export type BulkDeleteCustomersBody = OpRequestBody>; export type GetCustomersQuery = OpQueryParams>; +export type EditCustomerOpeningBalanceBody = OpRequestBody>; +export type EditCustomerOpeningBalanceResponse = OpResponseBody>; export async function fetchCustomers( fetcher: ApiFetcher, @@ -73,3 +75,13 @@ export async function bulkDeleteCustomers( const post = fetcher.path(CUSTOMERS_ROUTES.BULK_DELETE).method('post').create(); await post(body); } + +export async function editCustomerOpeningBalance( + fetcher: ApiFetcher, + id: number, + values: EditCustomerOpeningBalanceBody +): Promise { + const put = fetcher.path(CUSTOMERS_ROUTES.OPENING_BALANCE).method('put').create(); + const { data } = await put({ id, ...values }); + return data; +} diff --git a/shared/sdk-ts/src/import.ts b/shared/sdk-ts/src/import.ts new file mode 100644 index 000000000..676d0979d --- /dev/null +++ b/shared/sdk-ts/src/import.ts @@ -0,0 +1,70 @@ +import type { ApiFetcher } from './fetch-utils'; +import { paths } from './schema'; + +export const IMPORT_ROUTES = { + FILE: '/api/import/file', + MAPPING: '/api/import/{import_id}/mapping', + PREVIEW: '/api/import/{import_id}/preview', + IMPORT: '/api/import/{import_id}/import', + SAMPLE: '/api/import/sample', + META: '/api/import/{import_id}', +} as const satisfies Record; + +export interface ImportMappingBody { + mapping: Array<{ group?: string; from: string; to: string }>; +} + +export interface ImportPreviewResponse { + [key: string]: unknown; +} + +export interface ImportFileMetaResponse { + [key: string]: unknown; +} + +export interface ImportProcessResponse { + resource?: string; + [key: string]: unknown; +} + +export async function fetchImportPreview( + fetcher: ApiFetcher, + importId: string +): Promise { + const get = fetcher + .path(IMPORT_ROUTES.PREVIEW) + .method('get') + .create(); + const { data } = await get({ import_id: importId } as never); + return (data ?? {}) as ImportPreviewResponse; +} + +export async function fetchImportFileMeta( + fetcher: ApiFetcher, + importId: string +): Promise { + const get = fetcher.path(IMPORT_ROUTES.META).method('get').create(); + const { data } = await get({ import_id: importId } as never); + return (data ?? {}) as ImportFileMetaResponse; +} + +export async function importMapping( + fetcher: ApiFetcher, + importId: string, + body: ImportMappingBody +): Promise { + const post = fetcher + .path(IMPORT_ROUTES.MAPPING) + .method('post') + .create(); + await post({ import_id: importId, ...body } as never); +} + +export async function importProcess( + fetcher: ApiFetcher, + importId: string +): Promise { + const post = fetcher.path(IMPORT_ROUTES.IMPORT).method('post').create(); + const { data } = await post({ import_id: importId } as never); + return (data ?? {}) as ImportProcessResponse; +} diff --git a/shared/sdk-ts/src/index.ts b/shared/sdk-ts/src/index.ts index 622a4e47e..fd59809c8 100644 --- a/shared/sdk-ts/src/index.ts +++ b/shared/sdk-ts/src/index.ts @@ -15,10 +15,12 @@ export * from './items'; export * from './branches'; export * from './warehouses'; export * from './expenses'; +export * from './import'; export * from './manual-journals'; export * from './roles'; export * from './users'; export * from './dashboard'; +export * from './export'; export * from './settings'; export * from './organization'; export * from './subscription'; @@ -37,7 +39,12 @@ export * from './sale-estimates'; export * from './sale-receipts'; export * from './payment-receives'; export * from './payment-mades'; +export * from './payment-links'; +export * from './payment-services'; +export * from './plaid'; +export * from './stripe-integration'; export * from './inventory-adjustments'; +export * from './inventory-cost'; export * from './warehouse-transfers'; export * from './landed-cost'; export * from './generic-resource'; diff --git a/shared/sdk-ts/src/inventory-cost.ts b/shared/sdk-ts/src/inventory-cost.ts new file mode 100644 index 000000000..9aca6d6c9 --- /dev/null +++ b/shared/sdk-ts/src/inventory-cost.ts @@ -0,0 +1,34 @@ +import type { OpArgType } from 'openapi-typescript-fetch'; +import type { ApiFetcher } from './fetch-utils'; +import { paths } from './schema'; +import { OpForPath, OpQueryParams } from './utils'; + +export const INVENTORY_COST_ROUTES = { + ITEMS: '/api/inventory-cost/items', +} as const satisfies Record; + +type GetItemsCostOp = OpForPath; +type GetItemsCostArg = OpArgType; + +/** Query params for get items inventory cost. */ +export type GetInventoryItemsCostQuery = OpQueryParams; + +export interface InventoryItemCostMeta { + itemId: number; + valuation: number; + quantity: number; + average: number; +} + +export interface GetInventoryItemsCostResponse { + costs: InventoryItemCostMeta[]; +} + +export async function fetchInventoryCostItems( + fetcher: ApiFetcher, + query: GetInventoryItemsCostQuery +): Promise { + const get = fetcher.path(INVENTORY_COST_ROUTES.ITEMS).method('get').create(); + const { data } = await get(query as GetItemsCostArg); + return (data ?? { costs: [] }) as GetInventoryItemsCostResponse; +} diff --git a/shared/sdk-ts/src/landed-cost.ts b/shared/sdk-ts/src/landed-cost.ts index 32790bfe5..88e763eb7 100644 --- a/shared/sdk-ts/src/landed-cost.ts +++ b/shared/sdk-ts/src/landed-cost.ts @@ -1,6 +1,6 @@ import type { ApiFetcher } from './fetch-utils'; import { paths } from './schema'; -import { OpForPath, OpQueryParams, OpResponseBody } from './utils'; +import { OpForPath, OpQueryParams, OpRequestBody, OpResponseBody } from './utils'; export const LANDED_COST_ROUTES = { TRANSACTIONS: '/api/landed-cost/transactions', @@ -11,6 +11,10 @@ export const LANDED_COST_ROUTES = { export type LandedCostTransactionsResponse = OpResponseBody>; export type GetLandedCostTransactionsQuery = OpQueryParams>; +export type AllocateLandedCostBody = OpRequestBody>; +export type BillLandedCostTransactionsResponse = OpResponseBody< + OpForPath +>; export async function fetchLandedCostTransactions( fetcher: ApiFetcher, @@ -22,3 +26,29 @@ export async function fetchLandedCostTransactions( )(query ?? {}); return data; } + +export async function allocateLandedCost( + fetcher: ApiFetcher, + billId: number, + body: AllocateLandedCostBody +): Promise { + const post = fetcher.path(LANDED_COST_ROUTES.ALLOCATE).method('post').create(); + await post({ billId, ...body } as never); +} + +export async function deleteAllocatedLandedCost( + fetcher: ApiFetcher, + allocatedLandedCostId: number +): Promise { + const del = fetcher.path(LANDED_COST_ROUTES.BY_ID).method('delete').create(); + await del({ allocatedLandedCostId }); +} + +export async function fetchBillLandedCostTransactions( + fetcher: ApiFetcher, + billId: number +): Promise { + const get = fetcher.path(LANDED_COST_ROUTES.BILL_TRANSACTIONS).method('get').create(); + const { data } = await get({ billId }); + return data; +} diff --git a/shared/sdk-ts/src/organization.ts b/shared/sdk-ts/src/organization.ts index 997352837..3c1ed0243 100644 --- a/shared/sdk-ts/src/organization.ts +++ b/shared/sdk-ts/src/organization.ts @@ -12,6 +12,11 @@ export const ORGANIZATION_ROUTES = { export type OrganizationCurrent = OpResponseBody>; export type UpdateOrganizationBody = OpRequestBody>; +export type BuildOrganizationBody = OpRequestBody>; +export type BuildOrganizationResponse = OpResponseBody>; +export type OrgBaseCurrencyMutateAbilitiesResponse = OpResponseBody< + OpForPath +>; export async function fetchOrganizationCurrent(fetcher: ApiFetcher): Promise { const get = fetcher.path(ORGANIZATION_ROUTES.CURRENT).method('get').create(); @@ -19,6 +24,23 @@ export async function fetchOrganizationCurrent(fetcher: ApiFetcher): Promise { + const post = fetcher.path(ORGANIZATION_ROUTES.BUILD).method('post').create(); + const { data } = await post(values as never); + return data; +} + +export async function fetchOrgBaseCurrencyMutateAbilities( + fetcher: ApiFetcher +): Promise { + const get = fetcher.path(ORGANIZATION_ROUTES.BASE_CURRENCY_MUTATE).method('get').create(); + const { data } = await get({}); + return data; +} + export async function updateOrganization( fetcher: ApiFetcher, values: UpdateOrganizationBody diff --git a/shared/sdk-ts/src/payment-links.ts b/shared/sdk-ts/src/payment-links.ts new file mode 100644 index 000000000..ebaa34915 --- /dev/null +++ b/shared/sdk-ts/src/payment-links.ts @@ -0,0 +1,72 @@ +import type { ApiFetcher } from './fetch-utils'; +import { paths } from './schema'; +import { OpForPath, OpResponseBody, OpResponseBodyPdf } from './utils'; + +export const PAYMENT_LINKS_ROUTES = { + GET_INVOICE: '/api/payment-links/{paymentLinkId}/invoice', + CREATE_STRIPE_CHECKOUT_SESSION: + '/api/payment-links/{paymentLinkId}/stripe_checkout_session', + GET_INVOICE_PDF: '/api/payment-links/{paymentLinkId}/invoice/pdf', +} as const satisfies Record; + +type GetInvoiceOp = OpForPath< + (typeof PAYMENT_LINKS_ROUTES)['GET_INVOICE'], + 'get' +>; +type CreateCheckoutSessionOp = OpForPath< + (typeof PAYMENT_LINKS_ROUTES)['CREATE_STRIPE_CHECKOUT_SESSION'], + 'post' +>; +type GetInvoicePdfOp = OpForPath< + (typeof PAYMENT_LINKS_ROUTES)['GET_INVOICE_PDF'], + 'get' +>; + +export type GetInvoicePaymentLinkResponse = GetInvoiceOp extends { + responses: { 200: { content: { 'application/json': infer R } } }; +} + ? R extends { data?: infer D } + ? D + : R + : unknown; + +export type CreateStripeCheckoutSessionResponse = + OpResponseBody; +export type GetPaymentLinkInvoicePdfResponse = OpResponseBodyPdf; + +export async function fetchGetInvoicePaymentLink( + fetcher: ApiFetcher, + paymentLinkId: string, +): Promise { + const get = fetcher + .path(PAYMENT_LINKS_ROUTES.GET_INVOICE) + .method('get') + .create(); + const { data } = await get({ paymentLinkId }); + const body = data as { data?: GetInvoicePaymentLinkResponse }; + return body?.data ?? (body as GetInvoicePaymentLinkResponse); +} + +export async function fetchCreateStripeCheckoutSession( + fetcher: ApiFetcher, + paymentLinkId: string, +): Promise { + const post = fetcher + .path(PAYMENT_LINKS_ROUTES.CREATE_STRIPE_CHECKOUT_SESSION) + .method('post') + .create(); + const { data } = await post({ paymentLinkId }); + return data as CreateStripeCheckoutSessionResponse; +} + +export async function fetchGetPaymentLinkInvoicePdf( + fetcher: ApiFetcher, + paymentLinkId: string, +): Promise { + const get = fetcher + .path(PAYMENT_LINKS_ROUTES.GET_INVOICE_PDF) + .method('get') + .create(); + const response = await get({ paymentLinkId }); + return (response.data as Blob) ?? response.data; +} diff --git a/shared/sdk-ts/src/payment-mades.ts b/shared/sdk-ts/src/payment-mades.ts index b1ae0a703..840a03722 100644 --- a/shared/sdk-ts/src/payment-mades.ts +++ b/shared/sdk-ts/src/payment-mades.ts @@ -57,3 +57,21 @@ export async function fetchBillPaymentEditPage( const { data } = await get({ billPaymentId }); return data; } + +export type BillPaymentNewPageEntriesResponse = unknown; + +export async function fetchBillPaymentNewPageEntries( + fetcher: ApiFetcher, + vendorId: number +): Promise { + const get = fetcher + .path(BILL_PAYMENTS_ROUTES.NEW_PAGE_ENTRIES) + .method('get') + .create(); + + const { data } = await ( + // @ts-ignore + get as (params: { query?: { vendorId: number } }) => Promise<{ data: BillPaymentNewPageEntriesResponse }> + )({ query: { vendorId } }); + return data; +} diff --git a/shared/sdk-ts/src/payment-services.ts b/shared/sdk-ts/src/payment-services.ts new file mode 100644 index 000000000..ba195bbb0 --- /dev/null +++ b/shared/sdk-ts/src/payment-services.ts @@ -0,0 +1,120 @@ +import type { ApiFetcher } from './fetch-utils'; +import { paths } from './schema'; + +export const PAYMENT_SERVICES_ROUTES = { + LIST: '/api/payment-services', + STATE: '/api/payment-services/state', + BY_ID: '/api/payment-services/{paymentServiceId}', + UPDATE_METHOD: '/api/payment-services/{paymentMethodId}', + DELETE_METHOD: '/api/payment-services/{paymentMethodId}', +} as const satisfies Record; + +export interface GetPaymentServicesResponse { + payment_services?: unknown; +} + +export interface GetPaymentServicesStateResponse { + stripe: { + is_stripe_account_created: boolean; + is_stripe_payment_enabled: boolean; + is_stripe_payout_enabled: boolean; + is_stripe_enabled: boolean; + is_stripe_server_configured: boolean; + stripe_account_id: string | null; + stripe_payment_method_id: number | null; + stripe_currencies: string[]; + stripe_publishable_key: string; + stripe_auth_link: string; + stripe_redirect_url: string; + }; +} + +export interface GetPaymentServiceResponse { + data?: unknown; +} + +export interface EditPaymentMethodOptionsBody { + bankAccountId?: number; + clearningAccountId?: number; + showVisa?: boolean; + showMasterCard?: boolean; + showDiscover?: boolean; + showAmer?: boolean; + showJcb?: boolean; + showDiners?: boolean; +} + +export interface UpdatePaymentMethodBody { + name?: string; + options?: EditPaymentMethodOptionsBody; +} + +export interface UpdatePaymentMethodResponse { + id: number; + message: string; +} + +export interface DeletePaymentMethodResponse { + id: number; + message: string; +} + +export async function fetchGetPaymentServices( + fetcher: ApiFetcher, +): Promise { + const get = fetcher + .path(PAYMENT_SERVICES_ROUTES.LIST) + .method('get') + .create(); + const { data } = await get({}); + return data as GetPaymentServicesResponse; +} + +export async function fetchGetPaymentServicesState( + fetcher: ApiFetcher, +): Promise { + const get = fetcher + .path(PAYMENT_SERVICES_ROUTES.STATE) + .method('get') + .create(); + const { data } = await get({}); + const wrapped = data as { data?: GetPaymentServicesStateResponse }; + return wrapped?.data ?? (data as GetPaymentServicesStateResponse); +} + +export async function fetchGetPaymentService( + fetcher: ApiFetcher, + paymentServiceId: number, +): Promise { + const get = fetcher + .path(PAYMENT_SERVICES_ROUTES.BY_ID) + .method('get') + .create(); + const { data } = await get({ paymentServiceId }); + return data as GetPaymentServiceResponse; +} + +export async function fetchUpdatePaymentMethod( + fetcher: ApiFetcher, + paymentMethodId: number, + body: UpdatePaymentMethodBody, +): Promise { + const post = fetcher + .path(PAYMENT_SERVICES_ROUTES.UPDATE_METHOD) + .method('post') + .create(); + const { data } = await post({ paymentMethodId, ...body } as never); + return data as UpdatePaymentMethodResponse; +} + +export async function fetchDeletePaymentMethod( + fetcher: ApiFetcher, + paymentMethodId: number, +): Promise { + const del = fetcher + .path(PAYMENT_SERVICES_ROUTES.DELETE_METHOD) + .method('delete') + .create(); + const { data } = await del({ paymentMethodId }); + return data as DeletePaymentMethodResponse; +} diff --git a/shared/sdk-ts/src/plaid.ts b/shared/sdk-ts/src/plaid.ts new file mode 100644 index 000000000..dbd220a73 --- /dev/null +++ b/shared/sdk-ts/src/plaid.ts @@ -0,0 +1,40 @@ +import type { ApiFetcher } from './fetch-utils'; +import { paths } from './schema'; + +export const PLAID_ROUTES = { + LINK_TOKEN: '/api/banking/plaid/link-token', + EXCHANGE_TOKEN: '/api/banking/plaid/exchange-token', +} as const satisfies Record; + +export interface PlaidLinkTokenResponse { + linkToken?: string; + [key: string]: unknown; +} + +export interface PlaidExchangeTokenBody { + publicToken: string; + institutionId?: string; + [key: string]: unknown; +} + +export async function fetchPlaidLinkToken( + fetcher: ApiFetcher +): Promise { + const post = fetcher + .path(PLAID_ROUTES.LINK_TOKEN) + .method('post') + .create(); + const { data } = await post({}); + return (data ?? {}) as PlaidLinkTokenResponse; +} + +export async function fetchPlaidExchangeToken( + fetcher: ApiFetcher, + body: PlaidExchangeTokenBody +): Promise { + const post = fetcher + .path(PLAID_ROUTES.EXCHANGE_TOKEN) + .method('post') + .create(); + await post(body as never); +} diff --git a/shared/sdk-ts/src/schema.ts b/shared/sdk-ts/src/schema.ts index 79fad9f3c..52bbc6584 100644 --- a/shared/sdk-ts/src/schema.ts +++ b/shared/sdk-ts/src/schema.ts @@ -1580,6 +1580,38 @@ export interface paths { patch?: never; trace?: never; }; + "/api/stripe/account": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["StripeIntegrationController_createAccount"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/stripe/account_session": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["StripeIntegrationController_createAccountSession"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; "/api/webhooks/stripe": { parameters: { query?: never; @@ -17130,6 +17162,48 @@ export interface operations { }; }; }; + StripeIntegrationController_createAccount: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { account_id: string }; + }; + }; + }; + }; + StripeIntegrationController_createAccountSession: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { account?: string }; + }; + }; + responses: { + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { client_secret: string }; + }; + }; + }; + }; StripePaymentWebhooksController_handleWebhook: { parameters: { query?: never; diff --git a/shared/sdk-ts/src/stripe-integration.ts b/shared/sdk-ts/src/stripe-integration.ts new file mode 100644 index 000000000..ffc46175c --- /dev/null +++ b/shared/sdk-ts/src/stripe-integration.ts @@ -0,0 +1,102 @@ +import type { ApiFetcher } from './fetch-utils'; +import { paths } from './schema'; + +export const STRIPE_INTEGRATION_ROUTES = { + GET_LINK: '/api/stripe/link', + CALLBACK: '/api/stripe/callback', + ACCOUNT_LINK: '/api/stripe/account_link', + ACCOUNT: '/api/stripe/account', + ACCOUNT_SESSION: '/api/stripe/account_session', +} as const; + +export interface GetStripeConnectLinkResponse { + url: string; +} + +export interface ExchangeStripeOAuthBody { + code: string; +} + +export interface CreateStripeAccountLinkBody { + stripeAccountId: string; +} + +export interface StripeAccountLinkResponse { + url: string; + created: number; + expires_at: number; + object: string; +} + +export interface CreateStripeAccountLinkResponse { + clientSecret: StripeAccountLinkResponse; +} + +export async function fetchGetStripeConnectLink( + fetcher: ApiFetcher, +): Promise { + const get = fetcher + .path(STRIPE_INTEGRATION_ROUTES.GET_LINK) + .method('get') + .create(); + const { data } = await get({}); + return data as GetStripeConnectLinkResponse; +} + +export async function fetchExchangeStripeOAuth( + fetcher: ApiFetcher, + body: ExchangeStripeOAuthBody, +): Promise { + const post = fetcher + .path(STRIPE_INTEGRATION_ROUTES.CALLBACK) + .method('post') + .create(); + await post(body as never); +} + +export async function fetchCreateStripeAccountLink( + fetcher: ApiFetcher, + body: CreateStripeAccountLinkBody, +): Promise { + const post = fetcher + .path(STRIPE_INTEGRATION_ROUTES.ACCOUNT_LINK) + .method('post') + .create(); + const { data } = await post(body as never); + return data as CreateStripeAccountLinkResponse; +} + +export interface CreateStripeAccountResponse { + account_id: string; +} + +export interface CreateStripeAccountSessionBody { + account?: string; +} + +export interface CreateStripeAccountSessionResponse { + client_secret: string; +} + +export async function fetchCreateStripeAccount( + fetcher: ApiFetcher, +): Promise { + const post = fetcher + .path(STRIPE_INTEGRATION_ROUTES.ACCOUNT) + .method('post') + .create(); + const { data } = await post({}); + return data as CreateStripeAccountResponse; +} + +export async function fetchCreateStripeAccountSession( + fetcher: ApiFetcher, + body: CreateStripeAccountSessionBody, +): Promise { + const post = fetcher + .path(STRIPE_INTEGRATION_ROUTES.ACCOUNT_SESSION) + .method('post') + .create(); + const { data } = await post(body as never); + return data as CreateStripeAccountSessionResponse; +} diff --git a/shared/sdk-ts/src/subscription.ts b/shared/sdk-ts/src/subscription.ts index 7b7379d95..8457a0b00 100644 --- a/shared/sdk-ts/src/subscription.ts +++ b/shared/sdk-ts/src/subscription.ts @@ -1,6 +1,6 @@ import type { ApiFetcher } from './fetch-utils'; import { paths } from './schema'; -import { OpForPath, OpResponseBody } from './utils'; +import { OpForPath, OpRequestBody, OpResponseBody } from './utils'; export const SUBSCRIPTION_ROUTES = { LIST: '/api/subscription', @@ -11,6 +11,9 @@ export const SUBSCRIPTION_ROUTES = { } as const satisfies Record; export type SubscriptionsListResponse = OpResponseBody>; +export type ChangeSubscriptionPlanBody = OpRequestBody>; +export type GetLemonCheckoutUrlBody = OpRequestBody>; +export type GetLemonCheckoutUrlResponse = OpResponseBody>; export async function fetchSubscriptions(fetcher: ApiFetcher): Promise { const get = fetcher.path(SUBSCRIPTION_ROUTES.LIST).method('get').create(); @@ -18,6 +21,15 @@ export async function fetchSubscriptions(fetcher: ApiFetcher): Promise { + const post = fetcher.path(SUBSCRIPTION_ROUTES.CHECKOUT_URL).method('post').create(); + const { data } = await post(body as never); + return data; +} + export async function cancelSubscription(fetcher: ApiFetcher): Promise { const post = fetcher.path(SUBSCRIPTION_ROUTES.CANCEL).method('post').create(); await post({}); @@ -27,3 +39,11 @@ export async function resumeSubscription(fetcher: ApiFetcher): Promise { const post = fetcher.path(SUBSCRIPTION_ROUTES.RESUME).method('post').create(); await post({}); } + +export async function changeSubscriptionPlan( + fetcher: ApiFetcher, + body: ChangeSubscriptionPlanBody +): Promise { + const post = fetcher.path(SUBSCRIPTION_ROUTES.CHANGE).method('post').create(); + await post(body as never); +} diff --git a/shared/sdk-ts/src/vendor-credits.ts b/shared/sdk-ts/src/vendor-credits.ts index 69bf04196..047bf7b35 100644 --- a/shared/sdk-ts/src/vendor-credits.ts +++ b/shared/sdk-ts/src/vendor-credits.ts @@ -154,7 +154,7 @@ export async function fetchRefundVendorCreditTransaction( .path(VENDOR_CREDITS_ROUTES.REFUND_BY_ID) .method('get') .create(); - const { data } = await get({ refundCreditId }); + const { data } = await get({ refundCreditId: String(refundCreditId) }); return data; } diff --git a/shared/sdk-ts/src/vendors.ts b/shared/sdk-ts/src/vendors.ts index f399995af..43983f4f6 100644 --- a/shared/sdk-ts/src/vendors.ts +++ b/shared/sdk-ts/src/vendors.ts @@ -17,6 +17,8 @@ export type EditVendorBody = OpRequestBody>; export type BulkDeleteVendorsBody = OpRequestBody>; export type GetVendorsQuery = OpQueryParams>; +export type EditVendorOpeningBalanceBody = OpRequestBody>; +export type EditVendorOpeningBalanceResponse = OpResponseBody>; export async function fetchVendors( fetcher: ApiFetcher, @@ -73,3 +75,13 @@ export async function bulkDeleteVendors( const post = fetcher.path(VENDORS_ROUTES.BULK_DELETE).method('post').create(); await post(body); } + +export async function editVendorOpeningBalance( + fetcher: ApiFetcher, + id: number, + values: EditVendorOpeningBalanceBody +): Promise { + const put = fetcher.path(VENDORS_ROUTES.OPENING_BALANCE).method('put').create(); + const { data } = await put({ id, ...values }); + return data; +}