From 189c1f44b91ade4b714bd2dcf2a0a2009d78e694 Mon Sep 17 00:00:00 2001 From: nicktytarenko Date: Wed, 9 Sep 2026 20:43:59 +0300 Subject: [PATCH 1/2] Add FundingPool service --- components/Funding/PaymentRequestButton.tsx | 4 +- .../modals/ContributeToFundraiseModal.tsx | 7 +-- services/funding-pool.service.ts | 60 +++++++++++++++++++ services/payment.service.ts | 28 +++++---- 4 files changed, 84 insertions(+), 15 deletions(-) create mode 100644 services/funding-pool.service.ts diff --git a/components/Funding/PaymentRequestButton.tsx b/components/Funding/PaymentRequestButton.tsx index 454d0a219..f822b9257 100644 --- a/components/Funding/PaymentRequestButton.tsx +++ b/components/Funding/PaymentRequestButton.tsx @@ -104,7 +104,9 @@ function PaymentRequestButtonInner({ try { // Create payment intent on our backend - const { clientSecret } = await PaymentService.createPaymentIntent(amountInRsc, fundraiseId); + const { clientSecret } = await PaymentService.createPaymentIntent(amountInRsc, { + fundraiseId, + }); // Confirm the payment with the payment method from Apple Pay/Google Pay const { error: confirmError, paymentIntent } = await stripe!.confirmCardPayment( diff --git a/components/modals/ContributeToFundraiseModal.tsx b/components/modals/ContributeToFundraiseModal.tsx index 5e3fb48ff..793108786 100644 --- a/components/modals/ContributeToFundraiseModal.tsx +++ b/components/modals/ContributeToFundraiseModal.tsx @@ -298,10 +298,9 @@ function ContributeToFundraiseModalInner({ const { stripe, cardElement } = stripeContext; // Step 1: Create payment intent with amount and fundraise ID (backend adds fees and handles contribution) - const { clientSecret } = await PaymentService.createPaymentIntent( - amountInRsc, - fundraise.id - ); + const { clientSecret } = await PaymentService.createPaymentIntent(amountInRsc, { + fundraiseId: fundraise.id, + }); // Step 2: Confirm payment with Stripe const { error: stripeError, paymentIntent: stripePaymentIntent } = diff --git a/services/funding-pool.service.ts b/services/funding-pool.service.ts new file mode 100644 index 000000000..f17addfd6 --- /dev/null +++ b/services/funding-pool.service.ts @@ -0,0 +1,60 @@ +import { ApiClient } from './client'; +import { roundRscAmount } from './lib/serviceUtils'; +import { FundingPool, transformFundingPool } from '@/types/grant'; +import { ID } from '@/types/root'; + +export interface CreateFundingPoolContributionParams { + amount: number; + useCredits?: boolean; +} + +export interface DistributeFundingPoolParams { + amount: number; + applicationId: ID; +} + +/** + * Service for RFP community FundingPool APIs. + * Distinct from FundraiseService — proposal crowdfunding stays on /api/fundraise/. + */ +export class FundingPoolService { + private static readonly POOL_BASE_PATH = '/api/funding_pool'; + + /** + * Contribute RSC (wallet balance or funding credits) into the pool. + * Pool contributions are RSC-only. + * + * @param poolId - Funding pool id + * @param params - Contribution amount and whether to use funding credits + * @returns The updated funding pool + */ + static async createContribution( + poolId: ID, + params: CreateFundingPoolContributionParams + ): Promise { + const response = await ApiClient.post( + `${this.POOL_BASE_PATH}/${poolId}/create_contribution/`, + { + amount: roundRscAmount(params.amount), + amount_currency: 'RSC', + use_credits: params.useCredits ?? false, + } + ); + return transformFundingPool(response); + } + + /** + * Allocate pool holdings into an open proposal fundraise via its grant application. + * + * @param poolId - Funding pool id + * @param params - RSC amount and application id (not post/fundraise id) + * @returns The updated funding pool + */ + static async distribute(poolId: ID, params: DistributeFundingPoolParams): Promise { + const response = await ApiClient.post(`${this.POOL_BASE_PATH}/${poolId}/distribute/`, { + amount: roundRscAmount(params.amount), + application_id: params.applicationId, + }); + return transformFundingPool(response); + } +} diff --git a/services/payment.service.ts b/services/payment.service.ts index 2a5ed90b1..30bec17d0 100644 --- a/services/payment.service.ts +++ b/services/payment.service.ts @@ -30,6 +30,10 @@ export interface PaymentIntentResponse { stripeAmountCents: number; } +export type PaymentIntentTarget = + | { fundraiseId: ID; fundingPoolId?: never } + | { fundingPoolId: ID; fundraiseId?: never }; + /** * Service for handling payment-related API calls. */ @@ -37,27 +41,31 @@ export class PaymentService { private static readonly BASE_PATH = '/api/payment'; /** - * Creates a payment intent for purchasing RSC and contributing to a fundraise. - * The backend will add fees to the amount and handle the contribution. + * Creates a payment intent for purchasing RSC and contributing to a fundraise + * or funding pool. The backend adds fees and handles the contribution. * * @param amount The RSC amount to purchase (without fees) - * @param fundraiseId The ID of the fundraise to contribute to + * @param target Exactly one of fundraiseId or fundingPoolId * @returns Promise containing the Stripe client secret and payment details */ static async createPaymentIntent( amount: number, - fundraiseId: ID + target: PaymentIntentTarget ): Promise { + const hasFundingPool = 'fundingPoolId' in target; + const body = { + amount: roundRscAmount(amount), + currency: 'RSC' as const, + ...(hasFundingPool + ? { funding_pool_id: target.fundingPoolId } + : { fundraise_id: target.fundraiseId }), + }; + const response = await ApiClient.post( `${this.BASE_PATH}/payment-intent/`, - { - amount: roundRscAmount(amount), - currency: 'RSC', - fundraise_id: fundraiseId, - } + body ); - // Transform snake_case to camelCase return { clientSecret: response.client_secret, paymentIntentId: response.payment_intent_id, From 77c78bf1069da23b03f0ac229b2eb44106c1f00d Mon Sep 17 00:00:00 2001 From: nicktytarenko Date: Thu, 10 Sep 2026 13:39:58 +0300 Subject: [PATCH 2/2] Refine comments in FundingPoolService: removed redundant note on service distinction and clarified parameter description in distribute method. --- services/funding-pool.service.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/services/funding-pool.service.ts b/services/funding-pool.service.ts index f17addfd6..06a263a6a 100644 --- a/services/funding-pool.service.ts +++ b/services/funding-pool.service.ts @@ -15,7 +15,6 @@ export interface DistributeFundingPoolParams { /** * Service for RFP community FundingPool APIs. - * Distinct from FundraiseService — proposal crowdfunding stays on /api/fundraise/. */ export class FundingPoolService { private static readonly POOL_BASE_PATH = '/api/funding_pool'; @@ -47,7 +46,7 @@ export class FundingPoolService { * Allocate pool holdings into an open proposal fundraise via its grant application. * * @param poolId - Funding pool id - * @param params - RSC amount and application id (not post/fundraise id) + * @param params - RSC amount and application id * @returns The updated funding pool */ static async distribute(poolId: ID, params: DistributeFundingPoolParams): Promise {