You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Stripe中实现一次性自定义金额付款+可选每月5美元订阅功能?

How to Add an Optional Monthly $5 Subscription to Your Stripe One-Time Donation Flow in Go

Got it, let's break this down properly. Your core needs are:

  • Charge the custom one-time amount immediately, no matter if the user opts into the subscription
  • Only start the $5 monthly subscription the following month if the checkbox is checked

Here's the step-by-step implementation:

1. Update the Checkout Session Creation Code

First, modify your existing code to track whether the user checked the subscription box using metadata (this gets passed to the Stripe Session so we can act on it later). We'll keep the one-time payment logic intact since that's already handling the immediate charge.

import (
    "strconv"
    "github.com/stripe/stripe-go/v78"
    "github.com/stripe/stripe-go/v78/checkout/session"
)

func CreateDonationSession(amount int64, subscribeMonthly bool) (*stripe.CheckoutSession, error) {
    stripeParams := &stripe.CheckoutSessionParams{
        PaymentMethodTypes: stripe.StringSlice([]string{
            "card",
        }),
        LineItems: []*stripe.CheckoutSessionLineItemParams{
            &stripe.CheckoutSessionLineItemParams{
                PriceData: &stripe.CheckoutSessionLineItemPriceDataParams{
                    Currency: stripe.String(string(stripe.CurrencyUSD)),
                    ProductData: &stripe.CheckoutSessionLineItemPriceDataProductDataParams{
                        Name: stripe.String("Custom One-Time Donation"),
                    },
                    UnitAmount: stripe.Int64(amount * 100), // Stripe uses cents
                },
                Quantity: stripe.Int64(1),
            },
        },
        Mode: stripe.String(string(stripe.CheckoutSessionModePayment)),
        SuccessURL: stripe.String("https://yourdomain.com/success"), // Replace with your actual success URL
        CancelURL: stripe.String("https://yourdomain.com/cancel"),   // Replace with your actual cancel URL
        // Add metadata to track if the user wants the monthly subscription
        Metadata: map[string]string{
            "subscribe_monthly": strconv.FormatBool(subscribeMonthly),
            "one_time_amount":   strconv.FormatInt(amount, 10),
        },
    }

    return session.New(stripeParams)
}

2. Set Up a Webhook to Handle Successful Payments

You must use a Stripe webhook to create the subscription—this ensures we only create it after the one-time payment is confirmed (avoids creating subscriptions for failed payments). We'll listen for the checkout.session.completed event.

First, create a recurring $5 Price in your Stripe Dashboard (go to Products > Create Product, set it to recurring monthly, $5). Grab the Price ID (looks like price_XXXXXX) for the code below.

Webhook Handler Code

import (
    "encoding/json"
    "net/http"
    "strconv"
    "time"

    "github.com/stripe/stripe-go/v78"
    "github.com/stripe/stripe-go/v78/subscription"
)

func StripeWebhookHandler(w http.ResponseWriter, r *http.Request) {
    // 1. Verify the webhook signature (critical for security—don't skip this!)
    sigHeader := r.Header.Get("Stripe-Signature")
    payload := readRequestBody(r) // Implement this function to read the full request body
    event, err := stripe.WebhookConstructEvent(payload, sigHeader, "YOUR_WEBHOOK_SECRET") // Replace with your webhook secret
    if err != nil {
        w.WriteHeader(http.StatusBadRequest)
        return
    }

    // 2. Handle the successful checkout session event
    if event.Type == "checkout.session.completed" {
        var session stripe.CheckoutSession
        err := json.Unmarshal(event.Data.Raw, &session)
        if err != nil {
            w.WriteHeader(http.StatusInternalServerError)
            return
        }

        // 3. Check if the user opted into the monthly subscription
        subscribeStr, ok := session.Metadata["subscribe_monthly"]
        if !ok {
            w.WriteHeader(http.StatusOK)
            return
        }
        subscribeMonthly, err := strconv.ParseBool(subscribeStr)
        if err != nil || !subscribeMonthly {
            w.WriteHeader(http.StatusOK)
            return
        }

        // 4. Create the monthly subscription with next-month billing
        billingAnchor := getNextMonthAnchor()

        subParams := &stripe.SubscriptionParams{
            Customer: stripe.String(session.Customer.ID),
            Items: []*stripe.SubscriptionItemParams{
                {
                    Price: stripe.String("price_XXXXXX"), // Replace with your $5 monthly Price ID
                },
            },
            // Set first billing date to the same day next month
            BillingCycleAnchor: stripe.Int64(billingAnchor),
            // Ensure no immediate charge—subscription starts next cycle
            PaymentBehavior: stripe.String(string(stripe.SubscriptionPaymentBehaviorDefaultIncomplete)),
            // Expand to check payment status if needed
            Expand: []string{"latest_invoice.payment_intent"},
        }

        _, err = subscription.New(subParams)
        if err != nil {
            // Handle error (e.g., log it, notify your team, set up retry logic)
            w.WriteHeader(http.StatusInternalServerError)
            return
        }
    }

    w.WriteHeader(http.StatusOK)
}

// Helper function to calculate next month's billing timestamp
func getNextMonthAnchor() int64 {
    now := time.Now()
    nextMonth := now.AddDate(0, 1, 0)
    // Set to the same day next month; adjust for months with fewer days (e.g., 31st April)
    nextMonth = time.Date(nextMonth.Year(), nextMonth.Month(), now.Day(), 0, 0, 0, 0, now.Location())
    // If nextMonth is before today (e.g., today is 31st March), use last day of next month
    if nextMonth.Before(now) {
        nextMonth = nextMonth.AddDate(0, 0, -nextMonth.Day()+1).AddDate(0, 1, -1)
    }
    return nextMonth.Unix()
}

// Helper to read the full request body
func readRequestBody(r *http.Request) []byte {
    // Implement this: read the entire request body into a byte slice
    // Make sure to handle errors (e.g., r.Body.Read() failures)
}

Key Details to Note

  • Webhook Security: Always verify the Stripe webhook signature—this blocks fake requests from hitting your endpoint. You can get your webhook secret from the Stripe Dashboard's Webhooks settings.
  • Pre-Created Price: Creating the $5 monthly Price in the Stripe Dashboard is more consistent than generating it in code every time, and it's easier to update later if needed.
  • Billing Cycle Adjustment: The getNextMonthAnchor function ensures the first subscription charge happens the same day next month, even for months with fewer days (like February or April).
  • Error Handling: Log subscription creation failures and set up retry logic if needed—Stripe automatically retries failed subscription charges, but you should alert your team to critical issues.

内容的提问来源于stack exchange,提问作者Nilcale

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.29 23:47:31