Overview#
Open Collective's payment layer is built around three distinct model types, each serving a different role in the money flow:
| Model | Direction | Purpose |
|---|---|---|
PaymentMethod | Inbound | How contributors pay into a Collective |
PayoutMethod | Outbound | How payees receive expense payments |
ManualPaymentProvider | Inbound (manual) | Host-defined instructions for off-platform payments |
Payment processing logic lives in server/paymentProviders/ , with four provider implementations: stripe, paypal, opencollective, and transferwise. All providers implement the BasePaymentProviderService interface defined in server/paymentProviders/types.ts.
PaymentMethod β Inbound Contributions#
server/models/PaymentMethod.ts represents a payment instrument used to fund orders (contributions).
Key fields:
serviceβ one ofstripe,paypal,opencollective,wise(deprecated:thegivingblock)typeβ one of ~20 values includingcreditcard,giftcard,manual,collective,bank_transfer,virtual_card,us_bank_account,sepa_debit, and moreCollectiveIdβ the owner collective; can beNULLfor one-time, unsaved paymentslimitedToTags,limitedToHostCollectiveIdsβ restrict usage to specific collectives or hostsmonthlyLimitPerMember,initialBalanceβ spending caps enforced at use time
Core logic:
canBeUsedForOrder()validates expiry, recurring support, tag/host restrictions, and available balance before allowing an order to proceed.getBalanceForUser()delegates to the provider'sgetBalance()method, then appliesmonthlyLimitPerMemberandinitialBalancecaps.- The
featuresgetter callsfindPaymentMethodProvider(this).featuresto expose provider capabilities like recurring support.
PayoutMethod β Outbound Expense Payments#
server/models/PayoutMethod.ts represents where a payee wants to receive expense payments.
Types :
BANK_ACCOUNTβ Wise-compatible bank account (usesRecipientAccountfrom transferwise types)PAYPALβ PayPal email; supports OAuth verification flowACCOUNT_BALANCEβ Pay to the payee's Open Collective balanceSTRIPEβ Created automatically by ConnectedAccount hooks; cannot be manually edited or deletedCREDIT_CARDβ Token-based card payoutOTHERβ Free-text custom instructions
Key behaviors:
getFilteredData()whitelist-filters sensitive fields (IBAN, account numbers, etc.) before the data is sent to authorized users.createFromUserData()whitelists user input before persisting β prevents over-posting.findSimilar()detects duplicate payout methods using identifiable fields (IBAN,accountNumber,BIC, etc.) defined inIDENTIFIABLE_DATA_FIELDS.- Only
BANK_ACCOUNT,OTHER, andSTRIPEtypes support fees-payer configuration . - A payout method cannot be edited/deleted once it has been used in a non-cancelled/non-rejected expense .
ManualPaymentProvider β Host-Configured Manual Methods#
server/models/ManualPaymentProvider.ts models payment instructions that a fiscal host defines for contributors who cannot use automated processors (e.g., bank wire, checks). Unlike PayoutMethod (which belongs to a payee), this model belongs to the host and is presented to contributors at checkout.
Types : BANK_TRANSFER and OTHER
Key fields :
CollectiveIdβ the fiscal host that owns this providername,instructions(HTML, XSS-sanitized),iconβ displayed to contributors at checkoutdata.accountDetailsβ optional structured bank account details (WiseRecipientAccountformat)orderβ integer controlling display order; supports drag-and-drop reorderingarchivedAtβ soft-disable when orders reference the provider (cannot be hard-deleted in that case)
Lifecycle:
- Can be hard-deleted only if no orders reference it; otherwise
archive()setsarchivedAt. - Contributions made via a
ManualPaymentProviderstay in pending status until a host admin manually confirms them .
GraphQL mutations in server/graphql/v2/mutation/ManualPaymentProviderMutations.ts cover create, update, delete/archive, and reorder. All mutations require host admin scope and enforce 2FA . The reorderManualPaymentProviders mutation validates that all providers belong to the same host before updating their order field .
Payment Provider Implementations (Automated)#
The four automated payment providers are registered in server/paymentProviders/index.ts and each lives in its own subdirectory: stripe/, paypal/, transferwise/, opencollective/.
Every provider must implement BasePaymentProviderService:
| Method | Required | Description |
|---|---|---|
features.recurring | β | Whether subscriptions are supported |
features.isRecurringManagedExternally | optional | Recurring managed outside OC |
processOrder(order) | β | Charge and create Transaction(s) |
refundTransaction(transaction, ...) | β | Issue a refund via the provider API |
getBalance(paymentMethod) | optional | Return current balance |
updateBalance(paymentMethod) | optional | Sync balance from provider |
For providers with externally-managed recurring (e.g., Stripe subscriptions), the interface variant adds pauseSubscription() and resumeSubscription() methods. When implementing a new provider with external recurring, the freeze-account UI (FreezeAccountModal.tsx) and AccountMutations.ts must also be updated .
To add a new provider: create a class extending the appropriate BasePaymentProviderService variant in server/paymentProviders/{providerName}/, then export it from server/paymentProviders/index.ts .
Host-Level Configuration UI#
Fiscal hosts configure payment methods through two settings sections in the Dashboard:
Receiving Money#
components/edit-collective/sections/ReceivingMoney.tsx renders two subsections:
- Automatic Payments β Stripe (
EditStripeAccount) and PayPal (EditPayPalAccountwithvariation="RECEIVING") - Manual Payments β Bank Transfers (
BankTransferMethods.tsx) and Custom Payment Methods (CustomPaymentMethods.tsx)- Manual payment editing is gated by the host's
plan.manualPaymentsfeature flag BankTransferMethodsmanagesBANK_TRANSFERtype providers;CustomPaymentMethodsmanagesOTHERtype providers
- Manual payment editing is gated by the host's
Sending Money#
components/edit-collective/sections/SendingMoney.tsx exposes:
- Wise (TransferWise) β for batch expense payouts; gated by
TRANSFERWISEfeature flag with an upgrade prompt if needed - PayPal β for PayPal expense payouts; gated by
PAYPAL_PAYOUTSfeature
Both BankTransferMethods and CustomPaymentMethods use the same set of GraphQL mutations from receive-money/gql.ts: createManualPaymentProvider, updateManualPaymentProvider, deleteManualPaymentProvider, and reorderManualPaymentProviders.
Key Entry Points#
| File | Purpose |
|---|---|
server/constants/paymentMethods.ts | PAYMENT_METHOD_SERVICE and PAYMENT_METHOD_TYPE enums |
server/models/PaymentMethod.ts | Inbound payment instruments; balance/eligibility logic |
server/models/PayoutMethod.ts | Outbound payout destinations; data filtering |
server/models/ManualPaymentProvider.ts | Host-configured manual contribution methods |
server/paymentProviders/index.ts | Provider registry (stripe, paypal, transferwise, opencollective) |
server/paymentProviders/types.ts | BasePaymentProviderService interface |
server/graphql/v2/mutation/ManualPaymentProviderMutations.ts | CRUD + reorder mutations for manual payment providers |
components/edit-collective/sections/ReceivingMoney.tsx | Host "Receiving Money" settings UI |
components/edit-collective/sections/SendingMoney.tsx | Host "Sending Money" settings UI (Wise, PayPal payouts) |
components/edit-collective/sections/receive-money/gql.ts | GraphQL fragments + mutations for manual payment provider UI |