purchaseProduct
purchaseProduct(product: EntityRef!, gift: ProductPurchaseGiftInput, buyer: BuyerInput, discountCode: String, giftCardCode: String, paymentMethod: ID, savePaymentMethod: Boolean, taxCalculationId: String, forRegistration: Boolean, recurringConsentAccepted: Boolean, emailMarketingConsent: Boolean, emailMarketingConsentDisclosureVersion: ID, attribution: AttributionInput, idempotencyKey: String, fundingSource: CheckoutFundingSource, bankDebitMandate: ID): PurchaseProductResult!Purchase a product (pass, membership, gift card, or physical product). Returns a checkout session if payment is required, or the purchase directly for free products.
Pass buyer when the caller is not signed in to perform guest checkout. Requires the business's guestCheckoutEnabled setting. Passes and memberships always require a signed-in user.
Arguments
productEntityRef!buyerBuyerInputdiscountCodeStringOptional discount code typed by the customer. Validated server-side against the product's business; an invalid or unmet code returns a per-field error rather than purchasing at full price.
giftCardCodeStringOptional gift-card code to apply against the product price. The card's remaining balance covers up to the post-discount line; any remainder is charged via Stripe.
paymentMethodIDExisting saved Stripe PaymentMethod to charge. Currently used for membership subscriptions so a signed-in buyer can subscribe without re-entering card details.
savePaymentMethodBooleanWhen true for a signed-in one-shot card checkout, attach the newly-entered card to the buyer's Stripe Customer for future purchases. Ignored for guest checkout and memberships.
taxCalculationIdStringStripe Tax calculation ID returned by a recent quoteTax query. When provided, the server reuses that calculation rather than computing a fresh one — guaranteeing the buyer is charged the amount they saw. Ignored when the seller has tax disabled.
forRegistrationBooleanSet when this purchase exists only to fund a registration that's being booked in the same flow (the buyer picked "buy a pass" as the payment method on a session). For a single-use pass the spot consumes the only session immediately, so the standalone purchase-confirmation email is suppressed — the registration confirmation already covers it. Multi-use passes still send the purchase email since sessions remain.
recurringConsentAcceptedBooleanSeparate affirmative consent for recurring membership charges. Required when purchasing a membership; ignored for one-time products.
emailMarketingConsentBooleanEmail-marketing opt-in captured at checkout. true records an affirmative consent for this buyer against the product's business; only honored when the business has consent collection enabled. Sessions never sends marketing email itself — consent lets the business email the participant through its own provider.
emailMarketingConsentDisclosureVersionIDVersion of the consent disclosure the buyer was shown. Frozen with the consent record so the proof reflects exactly what they agreed to.
attributionAttributionInputMarketing attribution captured client-side. First-touch is stamped onto a newly created participant; last-touch onto the purchase. See registerForActivity.
idempotencyKeyStringOpaque client-generated idempotency token, stable for one checkout attempt (e.g. a single crypto.randomUUID() minted when the payment step mounts, NOT regenerated per click). When provided, it is used as the Stripe idempotencyKey on the saved-card off-session charge so a double-submit / client retry deduplicates to a single live charge instead of charging twice. Omit it to keep the legacy behaviour.
fundingSourceCheckoutFundingSourceWhich funding source the buyer picked, when the product offers a choice (see Membership.bankDebit — memberships only). Defaults to CARD. BANK is a request: the server re-checks the operator toggle, the rail, the connected account's capability, and the minimum amount, and silently bills the subscription to a card instead when any of them says no. Unrelated to paymentMethod, which is a saved Stripe PaymentMethod id.
bankDebitMandateIDA bank-debit mandate the buyer has already authorized, as the Stripe SetupIntent id returned by createBankDebitMandateSetup. Required for a rail that can't collect its mandate from a deferred-intent Payment Element (Canadian pre-authorized debit — see BankDebitCheckoutOption.mandateSetupRequired); ignored unless fundingSource is BANK.
With one, the subscription is created already billing the mandated bank account: its first invoice is confirmed server-side rather than by the client, and the returned checkout is only there so the caller can completeCheckout it. The server re-reads the SetupIntent from Stripe and requires it to have succeeded against this buyer's own customer, so an id from anywhere else is refused rather than trusted.
Returns
See PurchaseProductResult for the full shape.
Try it
Example
mutation PurchaseProduct($product: EntityRef!, $gift: ProductPurchaseGiftInput, $buyer: BuyerInput, $discountCode: String, $giftCardCode: String, $paymentMethod: ID, $savePaymentMethod: Boolean, $taxCalculationId: String, $forRegistration: Boolean, $recurringConsentAccepted: Boolean, $emailMarketingConsent: Boolean, $emailMarketingConsentDisclosureVersion: ID, $attribution: AttributionInput, $idempotencyKey: String, $fundingSource: CheckoutFundingSource, $bankDebitMandate: ID) {
purchaseProduct(product: $product, gift: $gift, buyer: $buyer, discountCode: $discountCode, giftCardCode: $giftCardCode, paymentMethod: $paymentMethod, savePaymentMethod: $savePaymentMethod, taxCalculationId: $taxCalculationId, forRegistration: $forRegistration, recurringConsentAccepted: $recurringConsentAccepted, emailMarketingConsent: $emailMarketingConsent, emailMarketingConsentDisclosureVersion: $emailMarketingConsentDisclosureVersion, attribution: $attribution, idempotencyKey: $idempotencyKey, fundingSource: $fundingSource, bankDebitMandate: $bankDebitMandate) {
__typename
}
}
https://go.sessions.website/api/graphql