Upsells और downsells आपको संयोजित भुगतान तरीके का उपयोग करके ग्राहकों को अतिरिक्त उत्पाद या योजना परिवर्तन प्रदान करने देता है। इससे एक-क्लिक खरीदारी संभव होती है जो भुगतान वसूली को छोड़ देती है, और रूपांतरण दरों में जबरदस्त सुधार होता है।
Post-Purchase Upsells
चेकआउट के तुरंत बाद पूरक उत्पाद एक-क्लिक खरीदारी के साथ पेश करें।
Subscription Upgrades
स्वचालित प्रोरैशन और तात्कालिक बिलिंग के साथ ग्राहकों को उच्चतर स्तर में स्थानांतरित करें।
Cross-Sells
मौजूदा ग्राहकों को संबंधित उत्पाद जोड़ें बिना भुगतान विवरण फिर से दर्ज किए।
अवलोकन
Upsells और downsells शक्तिशाली राजस्व अनुकूलन रणनीतियाँ हैं:- Upsells: उच्च-मूल्य उत्पाद या अपग्रेड (उदा., Basic के बजाय Pro योजना) पेश करें
- Downsells: जब ग्राहक अस्वीकार या डाउनग्रेड करे तो कम कीमत वाला विकल्प पेश करें
- Cross-sells: पूरक उत्पाद सुझाएँ (उदा., ऐड-ऑन, संबंधित आइटम)
payment_method_id पैरामीटर के माध्यम से इन फ्लो को सक्षम करता है, जो आपको ग्राहक का भुगतान तरीका बिना कार्ड विवरण दोबारा दर्ज किए चार्ज करने देता है।
मुख्य लाभ
| लाभ | प्रभाव |
|---|---|
| एक-क्लिक खरीदारी | लौटते ग्राहकों के लिए भुगतान फॉर्म पूरी तरह छोड़ें |
| उच्च रूपांतरण | निर्णय के समय घर्षण कम करें |
| तात्कालिक प्रोसेसिंग | confirm: true के साथ चार्ज तुरंत संसाधित होते हैं |
| सतत UX | ग्राहक पूरे फ्लो के दौरान आपके ऐप में बने रहते हैं |
यह कैसे काम करता है
पूर्वापेक्षाएँ
Upsells और downsells लागू करने से पहले सुनिश्चित करें कि आपके पास है:1
Customer with Saved Payment Method
ग्राहकों ने कम से कम एक खरीद पूरी की होनी चाहिए। भुगतान तरीके स्वचालित रूप से सहेजे जाते हैं जब ग्राहक चेकआउट पूरी करते हैं।
2
Products Configured
Dodo Payments डैशबोर्ड में अपने upsell उत्पाद बनाएँ। ये एक बार के भुगतान, सदस्यता या ऐड-ऑन हो सकते हैं।
3
Webhook Endpoint
payment.succeeded, payment.failed, और subscription.plan_changed इवेंट्स को संभालने के लिए वेबहुक सेटअप करें।ग्राहक भुगतान तरीके प्राप्त करना
Upsell पेश करने से पहले ग्राहक के सहेजे गए भुगतान तरीके प्राप्त करें:- TypeScript
- Python
- Go
import DodoPayments from 'dodopayments';
const client = new DodoPayments({
bearerToken: process.env.DODO_PAYMENTS_API_KEY,
environment: 'live_mode',
});
async function getPaymentMethods(customerId: string) {
const paymentMethods = await client.customers.retrievePaymentMethods(customerId);
// Returns { items: [...] } — the list of saved payment methods.
// Each item has: payment_method_id, payment_method, payment_method_type, last_used_at,
// recurring_enabled, and card (last4_digits, card_network, card_type, expiry_month, expiry_year)
return paymentMethods;
}
// Example usage
const methods = await getPaymentMethods('cus_123');
console.log('Available payment methods:', methods);
// Use the first available method for upsell
const primaryMethod = methods.items[0]?.payment_method_id;
import os
from dodopayments import DodoPayments
client = DodoPayments(
bearer_token=os.environ.get("DODO_PAYMENTS_API_KEY"),
environment="live_mode",
)
def get_payment_methods(customer_id: str):
payment_methods = client.customers.retrieve_payment_methods(customer_id)
# Returns an object with an `items` list of saved payment methods.
# Each item has: payment_method_id, payment_method, payment_method_type, last_used_at,
# recurring_enabled, and card (last4_digits, card_network, card_type, expiry_month, expiry_year)
return payment_methods
# Example usage
methods = get_payment_methods("cus_123")
print("Available payment methods:", methods)
# Use the first available method for upsell
primary_method = methods.items[0].payment_method_id if methods.items else None
package main
import (
"context"
"fmt"
"github.com/dodopayments/dodopayments-go"
"github.com/dodopayments/dodopayments-go/option"
)
func getPaymentMethods(customerID string) ([]dodopayments.CustomerGetPaymentMethodsResponseItem, error) {
client := dodopayments.NewClient(
option.WithBearerToken(os.Getenv("DODO_PAYMENTS_API_KEY")),
)
resp, err := client.Customers.GetPaymentMethods(
context.TODO(),
customerID,
)
if err != nil {
return nil, err
}
return resp.Items, nil
}
func main() {
methods, err := getPaymentMethods("cus_123")
if err != nil {
panic(err)
}
fmt.Println("Available payment methods:", methods)
// Use the first available method for upsell
if len(methods) > 0 {
primaryMethod := methods[0].PaymentMethodID
fmt.Println("Primary method:", primaryMethod)
}
}
ग्राहक चेकआउट पूरी करते ही भुगतान तरीके स्वचालित रूप से सहेजे जाते हैं। आपको उन्हें स्पष्ट रूप से सहेजने की आवश्यकता नहीं है।
पोस्ट-खरीद एक-क्लिक Upsells
सफल खरीद के तुरंत बाद अतिरिक्त उत्पाद पेश करें। ग्राहक एक ही क्लिक में स्वीकार कर सकते हैं क्योंकि उनका भुगतान तरीका पहले से सहेजा गया है।कार्यान्वयन
- TypeScript
- Python
- Go
import DodoPayments from 'dodopayments';
const client = new DodoPayments({
bearerToken: process.env.DODO_PAYMENTS_API_KEY,
environment: 'live_mode',
});
async function createOneClickUpsell(
customerId: string,
paymentMethodId: string,
upsellProductId: string
) {
// Create checkout session with saved payment method
// confirm: true processes the payment immediately
const session = await client.checkoutSessions.create({
product_cart: [
{
product_id: upsellProductId,
quantity: 1
}
],
customer: {
customer_id: customerId
},
payment_method_id: paymentMethodId,
confirm: true, // Required when using payment_method_id
return_url: 'https://yourapp.com/upsell-success',
feature_flags: {
redirect_immediately: true // Skip success page
},
metadata: {
upsell_source: 'post_purchase',
original_order_id: 'order_123'
}
});
return session;
}
// Example: Offer premium add-on after initial purchase
async function handlePostPurchaseUpsell(customerId: string) {
// Get customer's payment methods
const methods = await client.customers.retrievePaymentMethods(customerId);
if (methods.items.length === 0) {
console.log('No saved payment methods available');
return null;
}
// Create the upsell with one-click checkout
const upsell = await createOneClickUpsell(
customerId,
methods.items[0].payment_method_id,
'prod_premium_addon'
);
console.log('Upsell processed:', upsell.session_id);
return upsell;
}
import os
from dodopayments import DodoPayments
client = DodoPayments(
bearer_token=os.environ.get("DODO_PAYMENTS_API_KEY"),
environment="live_mode",
)
def create_one_click_upsell(
customer_id: str,
payment_method_id: str,
upsell_product_id: str
):
"""Create a one-click upsell using saved payment method."""
# Create checkout session with saved payment method
# confirm=True processes the payment immediately
session = client.checkout_sessions.create(
product_cart=[
{
"product_id": upsell_product_id,
"quantity": 1
}
],
customer={
"customer_id": customer_id
},
payment_method_id=payment_method_id,
confirm=True, # Required when using payment_method_id
return_url="https://yourapp.com/upsell-success",
feature_flags={
"redirect_immediately": True # Skip success page
},
metadata={
"upsell_source": "post_purchase",
"original_order_id": "order_123"
}
)
return session
def handle_post_purchase_upsell(customer_id: str):
"""Offer premium add-on after initial purchase."""
# Get customer's payment methods
methods = client.customers.retrieve_payment_methods(customer_id)
if not methods.items:
print("No saved payment methods available")
return None
# Create the upsell with one-click checkout
upsell = create_one_click_upsell(
customer_id=customer_id,
payment_method_id=methods.items[0].payment_method_id,
upsell_product_id="prod_premium_addon"
)
print(f"Upsell processed: {upsell.session_id}")
return upsell
package main
import (
"context"
"fmt"
"os"
"github.com/dodopayments/dodopayments-go"
"github.com/dodopayments/dodopayments-go/option"
)
func createOneClickUpsell(
customerID string,
paymentMethodID string,
upsellProductID string,
) (*dodopayments.CheckoutSessionResponse, error) {
client := dodopayments.NewClient(
option.WithBearerToken(os.Getenv("DODO_PAYMENTS_API_KEY")),
)
// Create checkout session with saved payment method
// Confirm: true processes the payment immediately
session, err := client.CheckoutSessions.New(context.TODO(), dodopayments.CheckoutSessionNewParams{
CheckoutSessionRequest: dodopayments.CheckoutSessionRequestParam{
ProductCart: dodopayments.F([]dodopayments.ProductItemReqParam{
{
ProductID: dodopayments.F(upsellProductID),
Quantity: dodopayments.F(int64(1)),
},
}),
Customer: dodopayments.F[dodopayments.CustomerRequestUnionParam](
dodopayments.AttachExistingCustomerParam{
CustomerID: dodopayments.F(customerID),
},
),
PaymentMethodID: dodopayments.F(paymentMethodID),
Confirm: dodopayments.F(true), // Required when using payment_method_id
ReturnURL: dodopayments.F("https://yourapp.com/upsell-success"),
FeatureFlags: dodopayments.F(dodopayments.CheckoutSessionFlagsParam{
RedirectImmediately: dodopayments.F(true), // Skip success page
}),
Metadata: dodopayments.F(map[string]string{
"upsell_source": "post_purchase",
"original_order_id": "order_123",
}),
},
})
return session, err
}
func handlePostPurchaseUpsell(customerID string) (*dodopayments.CheckoutSessionResponse, error) {
client := dodopayments.NewClient(
option.WithBearerToken(os.Getenv("DODO_PAYMENTS_API_KEY")),
)
// Get customer's payment methods
resp, err := client.Customers.GetPaymentMethods(context.TODO(), customerID)
if err != nil {
return nil, err
}
if len(resp.Items) == 0 {
fmt.Println("No saved payment methods available")
return nil, nil
}
// Create the upsell with one-click checkout
upsell, err := createOneClickUpsell(
customerID,
resp.Items[0].PaymentMethodID,
"prod_premium_addon",
)
if err != nil {
return nil, err
}
fmt.Printf("Upsell processed: %s\n", upsell.SessionID)
return upsell, nil
}
जब
payment_method_id का उपयोग किया जाता है, तो confirm: true सेट करना और एक मौजूदा customer_id प्रदान करना आवश्यक है। भुगतान तरीका उसी ग्राहक का होना चाहिए।सदस्यता अपग्रेड्स
स्वचालित प्रोरैशन हैंडलिंग के साथ ग्राहकों को उच्च-स्तरीय सदस्यता योजनाओं में स्थानांतरित करें।प्रतिबद्धता से पहले पूर्वावलोकन
हमेशा योजना परिवर्तनों का पूर्वावलोकन करें ताकि ग्राहकों को ठीक से पता चले कि उन्हें कितना चार्ज किया जाएगा:- TypeScript
- Python
- Go
async function previewUpgrade(
subscriptionId: string,
newProductId: string
) {
const preview = await client.subscriptions.previewChangePlan(subscriptionId, {
product_id: newProductId,
quantity: 1,
proration_billing_mode: 'difference_immediately'
});
return {
immediateCharge: preview.immediate_charge?.summary,
newPlan: preview.new_plan,
effectiveAt: preview.immediate_charge?.effective_at
};
}
// Show customer the charge before confirming
const preview = await previewUpgrade('sub_123', 'prod_pro_plan');
console.log(`Upgrade will charge: ${preview.immediateCharge}`);
def preview_upgrade(subscription_id: str, new_product_id: str):
preview = client.subscriptions.preview_change_plan(
subscription_id=subscription_id,
product_id=new_product_id,
quantity=1,
proration_billing_mode="difference_immediately"
)
return {
"immediate_charge": preview.immediate_charge.summary if preview.immediate_charge else None,
"new_plan": preview.new_plan,
"effective_at": preview.immediate_charge.effective_at if preview.immediate_charge else None,
}
# Show customer the charge before confirming
preview = preview_upgrade("sub_123", "prod_pro_plan")
print(f"Upgrade will charge: {preview['immediate_charge']}")
func previewUpgrade(subscriptionID string, newProductID string) (map[string]interface{}, error) {
client := dodopayments.NewClient(
option.WithBearerToken(os.Getenv("DODO_PAYMENTS_API_KEY")),
)
preview, err := client.Subscriptions.PreviewChangePlan(
context.TODO(),
subscriptionID,
dodopayments.SubscriptionPreviewChangePlanParams{
UpdateSubscriptionPlanReq: dodopayments.UpdateSubscriptionPlanReqParam{
ProductID: dodopayments.F(newProductID),
Quantity: dodopayments.F(int64(1)),
ProrationBillingMode: dodopayments.F(dodopayments.UpdateSubscriptionPlanReqProrationBillingModeDifferenceImmediately),
},
},
)
if err != nil {
return nil, err
}
return map[string]interface{}{
"immediate_charge": preview.ImmediateCharge.Summary,
"new_plan": preview.NewPlan,
"effective_at": preview.ImmediateCharge.EffectiveAt,
}, nil
}
अपग्रेड को निष्पादित करें
- TypeScript
- Python
- Go
async function upgradeSubscription(
subscriptionId: string,
newProductId: string,
prorationMode: 'prorated_immediately' | 'difference_immediately' | 'full_immediately' | 'do_not_bill' = 'difference_immediately'
) {
// change-plan returns 200 with an empty body — resolving without an error means it was accepted.
await client.subscriptions.changePlan(subscriptionId, {
product_id: newProductId,
quantity: 1,
proration_billing_mode: prorationMode
});
// Re-read the subscription to observe the applied state.
return await client.subscriptions.retrieve(subscriptionId);
}
// Upgrade from Basic ($30) to Pro ($80)
// With difference_immediately: charges $50 instantly
const upgrade = await upgradeSubscription('sub_123', 'pdt_pro_plan');
console.log('Upgrade status:', upgrade.status);
def upgrade_subscription(
subscription_id: str,
new_product_id: str,
proration_mode: str = "difference_immediately"
):
# change_plan returns 200 with an empty body — returning without an error means it was accepted.
client.subscriptions.change_plan(
subscription_id=subscription_id,
product_id=new_product_id,
quantity=1,
proration_billing_mode=proration_mode
)
# Re-read the subscription to observe the applied state.
return client.subscriptions.retrieve(subscription_id)
# Upgrade from Basic ($30) to Pro ($80)
# With difference_immediately: charges $50 instantly
upgrade = upgrade_subscription("sub_123", "pdt_pro_plan")
print(f"Upgrade status: {upgrade.status}")
func upgradeSubscription(
subscriptionID string,
newProductID string,
prorationMode dodopayments.UpdateSubscriptionPlanReqProrationBillingMode,
) error {
client := dodopayments.NewClient(
option.WithBearerToken(os.Getenv("DODO_PAYMENTS_API_KEY")),
)
// ChangePlan returns no response body; a nil error means the change succeeded.
err := client.Subscriptions.ChangePlan(
context.TODO(),
subscriptionID,
dodopayments.SubscriptionChangePlanParams{
UpdateSubscriptionPlanReq: dodopayments.UpdateSubscriptionPlanReqParam{
ProductID: dodopayments.F(newProductID),
Quantity: dodopayments.F(int64(1)),
ProrationBillingMode: dodopayments.F(prorationMode),
},
},
)
return err
}
// Upgrade from Basic ($30) to Pro ($80)
// With DifferenceImmediately: charges $50 instantly
err := upgradeSubscription(
"sub_123",
"prod_pro_plan",
dodopayments.UpdateSubscriptionPlanReqProrationBillingModeDifferenceImmediately,
)
if err != nil {
panic(err)
}
fmt.Println("Upgrade succeeded")
प्रोरैशन मोड
अपग्रेड करते समय ग्राहकों को कैसे बिल किया जाएगा चुनें:| मोड | व्यवहार | इसके लिए सर्वोत्तम |
|---|---|---|
difference_immediately | मूल्य का अंतर तुरंत चार्ज करता है (30→80 = $50) | सरल अपग्रेड |
prorated_immediately | billing cycle में बचे समय के आधार पर चार्ज करता है | उचित समय-आधारित billing |
full_immediately | नए प्लान की पूरी कीमत चार्ज करता है और बचे समय को अनदेखा करता है | billing cycle रीसेट होता है |
do_not_bill | बिना किसी तत्काल चार्ज के plan change लागू करता है; नए प्लान का billing अगले renewal पर किया जाता है और मूल billing date बरकरार रहती है | सौजन्य अपग्रेड और निःशुल्क migration |
सरल अपग्रेड फ्लो के लिए
difference_immediately का उपयोग करें। जब आप वर्तमान योजना पर बाकी बचे समय को ध्यान में रखना चाहते हैं तो prorated_immediately का उपयोग करें।क्रॉस-सेल्स
मौजूदा ग्राहकों के लिए पूरक उत्पाद जोड़ें बिना उन्हें भुगतान विवरण फिर से दर्ज कराने के।कार्यान्वयन
- TypeScript
- Python
- Go
async function createCrossSell(
customerId: string,
paymentMethodId: string,
productId: string,
quantity: number = 1
) {
// Create a one-time payment using saved payment method
const payment = await client.payments.create({
product_cart: [
{
product_id: productId,
quantity: quantity
}
],
customer: { customer_id: customerId },
billing: { country: 'US', city: 'San Francisco', state: 'CA', street: '1 Market St', zipcode: '94105' },
payment_method_id: paymentMethodId,
return_url: 'https://yourapp.com/purchase-complete',
metadata: {
purchase_type: 'cross_sell',
source: 'product_recommendation'
}
});
return payment;
}
// Example: Customer bought a course, offer related ebook
async function offerRelatedProduct(customerId: string, relatedProductId: string) {
const methods = await client.customers.retrievePaymentMethods(customerId);
if (methods.items.length === 0) {
// Fall back to standard checkout
return client.checkoutSessions.create({
product_cart: [{ product_id: relatedProductId, quantity: 1 }],
customer: { customer_id: customerId },
return_url: 'https://yourapp.com/purchase-complete'
});
}
// One-click purchase
return createCrossSell(customerId, methods.items[0].payment_method_id, relatedProductId);
}
def create_cross_sell(
customer_id: str,
payment_method_id: str,
product_id: str,
quantity: int = 1
):
"""Create a one-time payment using saved payment method."""
payment = client.payments.create(
product_cart=[
{
"product_id": product_id,
"quantity": quantity
}
],
customer={"customer_id": customer_id},
billing={"country": "US", "city": "San Francisco", "state": "CA", "street": "1 Market St", "zipcode": "94105"},
payment_method_id=payment_method_id,
return_url="https://yourapp.com/purchase-complete",
metadata={
"purchase_type": "cross_sell",
"source": "product_recommendation"
}
)
return payment
def offer_related_product(customer_id: str, related_product_id: str):
"""Offer related product with one-click purchase if possible."""
methods = client.customers.retrieve_payment_methods(customer_id)
if not methods.items:
# Fall back to standard checkout
return client.checkout_sessions.create(
product_cart=[{"product_id": related_product_id, "quantity": 1}],
customer={"customer_id": customer_id},
return_url="https://yourapp.com/purchase-complete"
)
# One-click purchase
return create_cross_sell(customer_id, methods.items[0].payment_method_id, related_product_id)
func createCrossSell(
customerID string,
paymentMethodID string,
productID string,
quantity int64,
) (*dodopayments.PaymentNewResponse, error) {
client := dodopayments.NewClient(
option.WithBearerToken(os.Getenv("DODO_PAYMENTS_API_KEY")),
)
payment, err := client.Payments.New(context.TODO(), dodopayments.PaymentNewParams{
ProductCart: dodopayments.F([]dodopayments.OneTimeProductCartItemParam{
{
ProductID: dodopayments.F(productID),
Quantity: dodopayments.F(quantity),
},
}),
Customer: dodopayments.F[dodopayments.CustomerRequestUnionParam](
dodopayments.AttachExistingCustomerParam{CustomerID: dodopayments.F(customerID)},
),
Billing: dodopayments.F(dodopayments.BillingAddressParam{
Country: dodopayments.F(dodopayments.CountryCodeUs),
City: dodopayments.F("San Francisco"),
State: dodopayments.F("CA"),
Street: dodopayments.F("1 Market St"),
Zipcode: dodopayments.F("94105"),
}),
PaymentMethodID: dodopayments.F(paymentMethodID),
ReturnURL: dodopayments.F("https://yourapp.com/purchase-complete"),
Metadata: dodopayments.F(map[string]string{
"purchase_type": "cross_sell",
"source": "product_recommendation",
}),
})
return payment, err
}
func offerRelatedProduct(customerID string, relatedProductID string) (interface{}, error) {
client := dodopayments.NewClient(
option.WithBearerToken(os.Getenv("DODO_PAYMENTS_API_KEY")),
)
resp, err := client.Customers.GetPaymentMethods(context.TODO(), customerID)
if err != nil {
return nil, err
}
if len(resp.Items) == 0 {
// Fall back to standard checkout
return client.CheckoutSessions.New(context.TODO(), dodopayments.CheckoutSessionNewParams{
CheckoutSessionRequest: dodopayments.CheckoutSessionRequestParam{
ProductCart: dodopayments.F([]dodopayments.ProductItemReqParam{
{ProductID: dodopayments.F(relatedProductID), Quantity: dodopayments.F(int64(1))},
}),
Customer: dodopayments.F[dodopayments.CustomerRequestUnionParam](
dodopayments.AttachExistingCustomerParam{CustomerID: dodopayments.F(customerID)},
),
ReturnURL: dodopayments.F("https://yourapp.com/purchase-complete"),
},
})
}
// One-click purchase
return createCrossSell(customerID, resp.Items[0].PaymentMethodID, relatedProductID, 1)
}
सदस्यता डाउनग्रेड्स
जब ग्राहक निचले स्तर की योजना पर जाना चाहें, तो स्वचालित क्रेडिट के साथ बदलाव को सहजता से संभालें।डाउनग्रेड कैसे काम करते हैं
- ग्राहक डाउनग्रेड का अनुरोध करता है (Pro → Basic)
- सिस्टम वर्तमान योजना पर शेष मूल्य की गणना करता है
- भविष्य के नवीनीकरण के लिए सदस्यता में क्रेडिट जोड़ा जाता है
- ग्राहक तुरंत नई योजना पर चला जाता है
- TypeScript
- Python
- Go
async function downgradeSubscription(
subscriptionId: string,
newProductId: string
) {
// Preview the downgrade first
const preview = await client.subscriptions.previewChangePlan(subscriptionId, {
product_id: newProductId,
quantity: 1,
proration_billing_mode: 'difference_immediately'
});
console.log('Credit to be applied:', preview.immediate_charge.summary.customer_credits);
// Execute the downgrade. change-plan returns 200 with an empty body.
await client.subscriptions.changePlan(subscriptionId, {
product_id: newProductId,
quantity: 1,
proration_billing_mode: 'difference_immediately'
});
// Credits are automatically applied to future renewals
return await client.subscriptions.retrieve(subscriptionId);
}
// Downgrade from Pro ($80) to Basic ($30)
// $50 credit added to subscription, auto-applied on next renewal
const downgrade = await downgradeSubscription('sub_123', 'pdt_basic_plan');
def downgrade_subscription(subscription_id: str, new_product_id: str):
# Preview the downgrade first
preview = client.subscriptions.preview_change_plan(
subscription_id=subscription_id,
product_id=new_product_id,
quantity=1,
proration_billing_mode="difference_immediately"
)
print(f"Credit to be applied: {preview.immediate_charge.summary.customer_credits}")
# Execute the downgrade. change_plan returns 200 with an empty body.
client.subscriptions.change_plan(
subscription_id=subscription_id,
product_id=new_product_id,
quantity=1,
proration_billing_mode="difference_immediately"
)
# Credits are automatically applied to future renewals
return client.subscriptions.retrieve(subscription_id)
# Downgrade from Pro ($80) to Basic ($30)
# $50 credit added to subscription, auto-applied on next renewal
downgrade = downgrade_subscription("sub_123", "pdt_basic_plan")
func downgradeSubscription(subscriptionID string, newProductID string) error {
client := dodopayments.NewClient(
option.WithBearerToken(os.Getenv("DODO_PAYMENTS_API_KEY")),
)
// Preview the downgrade first
preview, err := client.Subscriptions.PreviewChangePlan(
context.TODO(),
subscriptionID,
dodopayments.SubscriptionPreviewChangePlanParams{
UpdateSubscriptionPlanReq: dodopayments.UpdateSubscriptionPlanReqParam{
ProductID: dodopayments.F(newProductID),
Quantity: dodopayments.F(int64(1)),
ProrationBillingMode: dodopayments.F(dodopayments.UpdateSubscriptionPlanReqProrationBillingModeDifferenceImmediately),
},
},
)
if err != nil {
return err
}
fmt.Printf("Customer credits to be applied: %v\n", preview.ImmediateCharge.Summary.CustomerCredits)
// Execute the downgrade (returns no response body; nil error means success)
return client.Subscriptions.ChangePlan(
context.TODO(),
subscriptionID,
dodopayments.SubscriptionChangePlanParams{
UpdateSubscriptionPlanReq: dodopayments.UpdateSubscriptionPlanReqParam{
ProductID: dodopayments.F(newProductID),
Quantity: dodopayments.F(int64(1)),
ProrationBillingMode: dodopayments.F(dodopayments.UpdateSubscriptionPlanReqProrationBillingModeDifferenceImmediately),
},
},
)
}
difference_immediately का उपयोग करके डाउनग्रेड से प्राप्त क्रेडिट सब्सक्रिप्शन-स्कोप्ड होते हैं और स्वचालित रूप से भविष्य के नवीनीकरणों पर लागू होते हैं। ये क्रेडिट-आधारित बिलिंग अधिकारों से अलग हैं।पूर्ण उदाहरण: पोस्ट-खरीद Upsell फ्लो
यहाँ एक सफल खरीद के बाद upsell पेश करने का पूर्ण कार्यान्वयन है:- TypeScript
- Python
import DodoPayments from 'dodopayments';
import express from 'express';
const client = new DodoPayments({
bearerToken: process.env.DODO_PAYMENTS_API_KEY,
environment: 'live_mode',
});
const app = express();
// Store for tracking upsell eligibility (use your database in production)
const eligibleUpsells = new Map<string, { customerId: string; productId: string }>();
// Webhook handler for initial purchase success
app.post('/webhooks/dodo', express.raw({ type: 'application/json' }), async (req, res) => {
const event = JSON.parse(req.body.toString());
switch (event.type) {
case 'payment.succeeded':
// Check if customer is eligible for upsell
const customerId = event.data.customer_id;
const productId = event.data.product_id;
// Define upsell rules (e.g., bought Basic, offer Pro)
const upsellProduct = getUpsellProduct(productId);
if (upsellProduct) {
eligibleUpsells.set(customerId, {
customerId,
productId: upsellProduct
});
}
break;
case 'payment.failed':
console.log('Payment failed:', event.data.payment_id);
// Handle failed upsell payment
break;
}
res.json({ received: true });
});
// API endpoint to check upsell eligibility
app.get('/api/upsell/:customerId', async (req, res) => {
const { customerId } = req.params;
const upsell = eligibleUpsells.get(customerId);
if (!upsell) {
return res.json({ eligible: false });
}
// Get payment methods
const methods = await client.customers.retrievePaymentMethods(customerId);
if (methods.items.length === 0) {
return res.json({ eligible: false, reason: 'no_payment_method' });
}
// Get product details for display
const product = await client.products.retrieve(upsell.productId);
res.json({
eligible: true,
product: {
id: product.product_id,
name: product.name,
price: product.price,
currency: product.price.currency
},
paymentMethodId: methods.items[0].payment_method_id
});
});
// API endpoint to accept upsell
app.post('/api/upsell/:customerId/accept', async (req, res) => {
const { customerId } = req.params;
const upsell = eligibleUpsells.get(customerId);
if (!upsell) {
return res.status(400).json({ error: 'No upsell available' });
}
try {
const methods = await client.customers.retrievePaymentMethods(customerId);
// Create one-click purchase
const session = await client.checkoutSessions.create({
product_cart: [{ product_id: upsell.productId, quantity: 1 }],
customer: { customer_id: customerId },
payment_method_id: methods.items[0].payment_method_id,
confirm: true,
return_url: `${process.env.APP_URL}/upsell-success`,
feature_flags: { redirect_immediately: true },
metadata: { upsell: 'true', source: 'post_purchase' }
});
// Clear the upsell offer
eligibleUpsells.delete(customerId);
res.json({ success: true, sessionId: session.session_id });
} catch (error) {
console.error('Upsell failed:', error);
res.status(500).json({ error: 'Upsell processing failed' });
}
});
// Helper function to determine upsell product
function getUpsellProduct(purchasedProductId: string): string | null {
const upsellMap: Record<string, string> = {
'prod_basic_plan': 'prod_pro_plan',
'prod_starter_course': 'prod_complete_bundle',
'prod_single_license': 'prod_team_license'
};
return upsellMap[purchasedProductId] || null;
}
app.listen(3000);
import os
from flask import Flask, request, jsonify
from dodopayments import DodoPayments
client = DodoPayments(
bearer_token=os.environ.get("DODO_PAYMENTS_API_KEY"),
environment="live_mode",
)
app = Flask(__name__)
# Store for tracking upsell eligibility (use your database in production)
eligible_upsells = {}
@app.route('/webhooks/dodo', methods=['POST'])
def webhook_handler():
event = request.json
if event['type'] == 'payment.succeeded':
# Check if customer is eligible for upsell
customer_id = event['data']['customer_id']
product_id = event['data']['product_id']
# Define upsell rules
upsell_product = get_upsell_product(product_id)
if upsell_product:
eligible_upsells[customer_id] = {
'customer_id': customer_id,
'product_id': upsell_product
}
elif event['type'] == 'payment.failed':
print(f"Payment failed: {event['data']['payment_id']}")
return jsonify({'received': True})
@app.route('/api/upsell/<customer_id>', methods=['GET'])
def check_upsell(customer_id):
upsell = eligible_upsells.get(customer_id)
if not upsell:
return jsonify({'eligible': False})
# Get payment methods
methods = client.customers.retrieve_payment_methods(customer_id)
if not methods.items:
return jsonify({'eligible': False, 'reason': 'no_payment_method'})
# Get product details for display
product = client.products.retrieve(upsell['product_id'])
return jsonify({
'eligible': True,
'product': {
'id': product.product_id,
'name': product.name,
'price': product.price,
'currency': product.price.currency
},
'payment_method_id': methods.items[0].payment_method_id
})
@app.route('/api/upsell/<customer_id>/accept', methods=['POST'])
def accept_upsell(customer_id):
upsell = eligible_upsells.get(customer_id)
if not upsell:
return jsonify({'error': 'No upsell available'}), 400
try:
methods = client.customers.retrieve_payment_methods(customer_id)
# Create one-click purchase
session = client.checkout_sessions.create(
product_cart=[{'product_id': upsell['product_id'], 'quantity': 1}],
customer={'customer_id': customer_id},
payment_method_id=methods.items[0].payment_method_id,
confirm=True,
return_url=f"{os.environ['APP_URL']}/upsell-success",
feature_flags={'redirect_immediately': True},
metadata={'upsell': 'true', 'source': 'post_purchase'}
)
# Clear the upsell offer
del eligible_upsells[customer_id]
return jsonify({'success': True, 'session_id': session.session_id})
except Exception as error:
print(f"Upsell failed: {error}")
return jsonify({'error': 'Upsell processing failed'}), 500
def get_upsell_product(purchased_product_id: str) -> str:
"""Determine upsell product based on purchased product."""
upsell_map = {
'prod_basic_plan': 'prod_pro_plan',
'prod_starter_course': 'prod_complete_bundle',
'prod_single_license': 'prod_team_license'
}
return upsell_map.get(purchased_product_id)
if __name__ == '__main__':
app.run(port=3000)
सर्वोत्तम प्रथाएँ
Time Your Upsells Strategically
Time Your Upsells Strategically
Upsell पेश करने का सबसे अच्छा समय सफल खरीद के तुरंत बाद होता है जब ग्राहक खरीदारी के मनोदशा में होते हैं। अन्य प्रभावी क्षण:
- फीचर उपयोग की माइलस्टोन के बाद
- योजना सीमाओं के पास जाते समय
- ऑनबोर्डिंग पूरा होने पर
Validate Payment Method Eligibility
Validate Payment Method Eligibility
एक-क्लिक चार्ज करने का प्रयास करने से पहले भुगतान तरीके की पुष्टि करें:
- क्या यह उत्पाद की मुद्रा के साथ संगत है
- क्या इसकी अवधि समाप्त नहीं हुई है
- क्या यह ग्राहक से ही जुड़ा है
Handle Failures Gracefully
Handle Failures Gracefully
जब एक-क्लिक चार्ज असफल हों:
- मानक चेकआउट फ्लो पर वापस जाएँ
- ग्राहक को स्पष्ट संदेश दें
- भुगतान तरीका अपडेट करने का विकल्प दें
- विफल चार्ज को बार-बार पुनः प्रयास न करें
Provide Clear Value Proposition
Provide Clear Value Proposition
जब ग्राहक मूल्य समझते हैं तो upsells बेहतर रूपांतरण करते हैं:
- वे क्या प्राप्त कर रहे हैं बनाम वर्तमान योजना दिखाएँ
- कुल कीमत नहीं, मूल्य अंतर को हाइलाइट करें
- सामाजिक प्रमाण उपयोग करें (उदा., “सबसे लोकप्रिय अपग्रेड”)
Respect Customer Choice
Respect Customer Choice
- हमेशा अस्वीकृत करने का आसान तरीका प्रदान करें
- अस्वीकृति के बाद एक ही upsell बार-बार न दिखाएँ
- यह ट्रैक और विश्लेषण करें कि किस upsell से रूपांतरण होता है ताकि ऑफ़र अनुकूलित हों
मॉनिटर करने के लिए वेबहुक्स
Upsell और डाउनग्रेड फ्लो के लिए इन वेबहुक इवेंट्स को ट्रैक करें:| इवेंट | ट्रिगर | क्रिया |
|---|---|---|
payment.succeeded | Upsell/क्रॉस-सेल भुगतान पूरा हुआ | उत्पाद वितरित करें, पहुंच अपडेट करें |
payment.failed | एक-क्लिक चार्ज असफल रहा | त्रुटि दिखाएँ, पुनः प्रयास या फॉलबैक का विकल्प दें |
subscription.plan_changed | अपग्रेड/डाउनग्रेड पूरा हुआ | फीचर्स अपडेट करें, पुष्टि भेजें |
subscription.active | योजना परिवर्तन के बाद सदस्यता फिर सक्रिय हुई | नए स्तर तक पहुंच दें |
Webhook Integration Guide
वेबहुक एंडपॉइंट कैसे सेट करें और सत्यापित करें जानें।
संबंधित संसाधन
Subscription Upgrade Guide
योजना परिवर्तनों, प्रोरैशन मोड, और असफलताओं को संभालने पर विस्तृत गाइड।
Checkout Sessions
सभी विकल्पों के साथ चेकआउट सत्र बनाने के लिए संपूर्ण संदर्भ।
Customer Payment Methods API
ग्राहक भुगतान तरीकों की सूची बनाने के लिए API संदर्भ।
Add-ons
अतिरिक्त राजस्व के लिए लचीले ऐड-ऑन के साथ सदस्यताओं को बढ़ाएँ।