Marketplaces¶
A spec with a marketplace section lets your sellers be paid directly, with the
platform keeping a fee. Each seller has a Stripe Express account: Stripe hosts
the onboarding and holds the seller's identity and bank details.
Onboarding a seller¶
acct, err := connect.CreateAccount(ctx, seller, "US") // store acct on the seller
link, err := connect.OnboardingLink(ctx, acct, refreshURL, returnURL)
// redirect the seller to link
CreateAccountmakes one account per seller; a repeat returns the same one. Stripe cannot search accounts, so store the ID. The country is the seller's two-letter code and cannot be changed later.- An onboarding link is single-use and expires in minutes. Stripe sends the
seller to
refreshURLwhen it has expired; that page should callOnboardingLinkagain. Account(ctx, id)reads the account:ChargesEnabled,PayoutsEnabled,DetailsSubmitted. Returning toreturnURLdoes not mean onboarding finished; check, or wait for theaccount.updatedevent.
A sale¶
sess, err := connect.CheckoutSale(ctx, payforge.Sale{
Buyer: buyer,
SellerAccount: acct,
SellerPlan: "seller",
Product: "listing",
AmountCents: 2599, // for a seller-set product
Reference: "order-8841",
SuccessURL: okURL,
CancelURL: cancelURL,
})
It is a destination charge made on the seller's behalf: the seller is the merchant on the buyer's statement, the money is transferred to the seller's account, and the platform fee is taken as the application fee.
- The fee is the seller plan's
platformFeeBpswhen it sets one, otherwise the marketplace's, applied to what the buyer pays and rounded down to a cent. A sale of 499 cents at 8% charges 39 cents, not 40. A fee of zero sends no application fee.payforge.FeeBpsandpayforge.Feecompute it for a pricing page. - A seller who cannot take charges is refused with
ErrSellerNotReadybefore any checkout is made. This reads the account, one API call per sale. - An account this app did not create is refused with
ErrForeignAccount. The money goes to the account named, so an account ID from a request, or a wrong record, cannot pay another app's seller on the same Stripe account. SellerPlanis trusted. It chooses the fee, so take it from your own record of the seller, never from a request.- For a fixed-price product the fee is computed from the spec's amount. Keep the catalog synced, or Stripe refuses a fee larger than a lower price.
- A spec without a marketplace cannot create accounts or sell
(
ErrNoMarketplace). - The fee is on the amount the seller set. Shipping or tax added at checkout is not part of it.
Refunds and account changes¶
Two more ports receive what happens afterwards. Both are optional; without one, its events are ignored (and not recorded, so you can add it later).
life := &payforge.Lifecycle{
Spec: spec, Store: store, Sink: sink, Provider: client,
Sales: saleSink, // refunds
Sellers: sellerSink, // connected-account changes
Accounts: client, // read the account's current state
}
type SaleSink interface {
ApplyRefund(ctx context.Context, r payforge.SaleRefund) error
}
type SellerSink interface {
ApplySeller(ctx context.Context, a payforge.SellerAccount) error
}
- Refunds.
SaleRefundnames the buyer, seller, product, charge and payment intent (to match the order you recorded) and the total refunded so far. A full refund (Full) means the buyer no longer has access to what was bought; keep the data. The total only grows, so keep the highest you have seen and treat a full refund as permanent: a late event for an earlier partial refund must not reopen the sale. Refunds of a subscription's invoice, or of another app's charge, are skipped. - Accounts.
SellerAccountis the whole current state. WithAccountsset, the lifecycle applies what Stripe says now, not what the event says, so a late event cannot turn charges back on. A seller can lose the ability to sell after onboarding; useChargesEnabledto decide whether to show their listings. Accounts payforge did not create are skipped.
payforgetest.Sales and payforgetest.Sellers check your implementations, as
payforgetest.Store does.
Sellers outside your Stripe region¶
CreateAccount requests both the card_payments and transfers capabilities,
which is what a sale made on the seller's behalf needs. Stripe supports that for
sellers in the platform's own region. Sellers elsewhere are onboarded under a
different agreement with transfers only, and a sale on their behalf is not
possible as written. Treat cross-border sellers as unsupported.
Not covered¶
- A one-time sale being paid. Record it yourself when your checkout completes
(
EventCheckoutCompletedwithPaid), and match a later refund by payment intent. - Delayed payment methods that settle days later: the success arrives as
EventCheckoutCompletedwithPaidand the failure asEventCheckoutFailed; both are yours to record. - Disputes reach a
DisputeSinkby charge, not by buyer; see disputes. - Payouts and transfer reversals; Stripe handles them and sends events payforge ignores.