> ## Documentation Index
> Fetch the complete documentation index at: https://docs.dodopayments.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Uppersell & Neddersell

> Öka intäkterna med engångsköp med sparade betalningsmetoder för erbjudanden efter köp, uppgraderingar av abonnemang och korsförsäljning.

<Info>
  Upsells och downsells låter dig erbjuda ytterligare produkter eller abonnemangsförändringar till kunder med deras sparade betalningsmetoder. Detta möjliggör köp med ett klick som hoppar över betalningsinsamling, vilket dramatiskt förbättrar konverteringsgraden.
</Info>

<CardGroup cols={3}>
  <Card title="Post-Purchase Upsells" icon="cart-plus">
    Erbjud kompletterande produkter direkt efter kassan med ett klick för att köpa.
  </Card>

  <Card title="Subscription Upgrades" icon="arrow-up">
    Flytta kunder till högre nivåer med automatisk proration och omedelbar fakturering.
  </Card>

  <Card title="Cross-Sells" icon="grid-2-plus">
    Lägg till relaterade produkter till befintliga kunder utan att återigen ange betalningsuppgifter.
  </Card>
</CardGroup>

## Översikt

Uppersell och neddersell är kraftfulla strategier för intäktsoptimering:

* **Uppersell**: Erbjud en produkt eller uppgradering med högre värde (t.ex. Pro-plan istället för Basic)
* **Neddersell**: Erbjud ett alternativ med lägre pris när en kund avböjer eller nedgraderas
* **Korsförsäljning**: Föreslå kompletterande produkter (t.ex. tillägg, relaterade artiklar)

Dodo Payments möjliggör dessa flöden genom `payment_method_id`-parametern, som låter dig debitera en kunds sparade betalningsmetod utan att kräva att de anger kortuppgifter igen.

### Nyckelfördelar

| Fördel                        | Effekt                                                       |
| ----------------------------- | ------------------------------------------------------------ |
| **Köp med ett klick**         | Hoppa över betalningsformuläret helt för återkommande kunder |
| **Högre konvertering**        | Minska friktionen i beslutsmomentet                          |
| **Omedelbar behandling**      | Debiteringar sker omedelbart med `confirm: true`             |
| **Sömlös användarupplevelse** | Kunder stannar kvar i din app under hela flödet              |

## Hur det fungerar

```mermaid theme={null}
sequenceDiagram
    participant C as Customer
    participant A as Your App
    participant D as Dodo API
    
    C->>A: Completes purchase
    A->>D: Get payment methods
    D-->>A: payment_method_id
    A->>C: Show upsell offer
    C->>A: Accept (one click)
    A->>D: Charge with payment_method_id
    D-->>A: Payment success
    D->>A: Webhook: payment.succeeded
```

## Förutsättningar

Innan du implementerar uppersell och neddersell, se till att du har:

<Steps>
  <Step title="Customer with Saved Payment Method">
    Kunder måste ha genomfört minst ett köp. Betalningsmetoder sparas automatiskt när kunder genomför kassan.
  </Step>

  <Step title="Products Configured">
    Skapa dina upsell-produkter i Dodo Payments-instrumentpanelen. Dessa kan vara engångsbetalningar, prenumerationer eller tillägg.
  </Step>

  <Step title="Webhook Endpoint">
    Ställ in webhookar för att hantera `payment.succeeded`, `payment.failed` och `subscription.plan_changed`-händelser.
  </Step>
</Steps>

## Hämta kundbetalningsmetoder

Innan du erbjuder en uppersell, hämta kundens sparade betalningsmetoder:

<Tabs>
  <Tab title="TypeScript">
    ```typescript theme={null}
    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 array of saved payment methods
      // Each has: payment_method_id, type, card (last4, brand, exp_month, exp_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[0]?.payment_method_id;
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    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 list of saved payment methods
        # Each has: payment_method_id, type, card (last4, brand, exp_month, exp_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[0].payment_method_id if methods else None
    ```
  </Tab>

  <Tab title="Go">
    ```go theme={null}
    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)
        }
    }
    ```
  </Tab>
</Tabs>

<Info>
  Betalningsmetoder sparas automatiskt när kunder slutför köpet. Du behöver inte spara dem uttryckligen.
</Info>

## Engångsupersell efter köp

Erbjud ytterligare produkter omedelbart efter ett lyckat köp. Kunden kan godkänna med ett enda klick eftersom deras betalningsmetod redan är sparad.

```mermaid theme={null}
flowchart LR
    A[Purchase] --> B{Upsell?}
    B -->|Yes| C[Show Offer]
    B -->|No| F[Done]
    C --> D{Accept?}
    D -->|Yes| E[One-Click Charge] --> F
    D -->|No| F
```

### Implementering

<Tabs>
  <Tab title="TypeScript">
    ```typescript theme={null}
    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.length === 0) {
        console.log('No saved payment methods available');
        return null;
      }

      // Create the upsell with one-click checkout
      const upsell = await createOneClickUpsell(
        customerId,
        methods[0].payment_method_id,
        'prod_premium_addon'
      );

      console.log('Upsell processed:', upsell.session_id);
      return upsell;
    }
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    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:
            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[0].payment_method_id,
            upsell_product_id="prod_premium_addon"
        )
        
        print(f"Upsell processed: {upsell.session_id}")
        return upsell
    ```
  </Tab>

  <Tab title="Go">
    ```go theme={null}
    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
    }
    ```
  </Tab>
</Tabs>

<Warning>
  När du använder `payment_method_id` måste du ange `confirm: true` och tillhandahålla en befintlig `customer_id`. Betalningsmetoden måste tillhöra den kunden.
</Warning>

## Uppgraderingar av abonnemang

Flytta kunder till abonnemangsplaner av högre nivå med automatisk proration.

```mermaid theme={null}
sequenceDiagram
    participant C as Customer
    participant A as Your App
    participant D as Dodo API
    
    C->>A: Request upgrade
    A->>D: Preview change plan
    D-->>A: Charge amount
    A->>C: Confirm $50 charge?
    C->>A: Confirm
    A->>D: Change plan
    D->>A: Webhook: plan_changed
```

### Förhandsgranska innan du åtar dig

Förhandsgranska alltid planändringar för att visa kunder exakt vad de kommer att debiteras:

<Tabs>
  <Tab title="TypeScript">
    ```typescript theme={null}
    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,
        effectiveDate: preview.effective_date
      };
    }

    // Show customer the charge before confirming
    const preview = await previewUpgrade('sub_123', 'prod_pro_plan');
    console.log(`Upgrade will charge: ${preview.immediateCharge}`);
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    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_date": preview.effective_date
        }

    # Show customer the charge before confirming
    preview = preview_upgrade("sub_123", "prod_pro_plan")
    print(f"Upgrade will charge: {preview['immediate_charge']}")
    ```
  </Tab>

  <Tab title="Go">
    ```go theme={null}
    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
    }
    ```
  </Tab>
</Tabs>

### Genomföra uppgraderingen

<Tabs>
  <Tab title="TypeScript">
    ```typescript theme={null}
    async function upgradeSubscription(
      subscriptionId: string,
      newProductId: string,
      prorationMode: 'prorated_immediately' | 'difference_immediately' | 'full_immediately' = 'difference_immediately'
    ) {
      const result = await client.subscriptions.changePlan(subscriptionId, {
        product_id: newProductId,
        quantity: 1,
        proration_billing_mode: prorationMode
      });

      return {
        status: result.status,
        subscriptionId: result.subscription_id,
        paymentId: result.payment_id,
        invoiceId: result.invoice_id
      };
    }

    // Upgrade from Basic ($30) to Pro ($80)
    // With difference_immediately: charges $50 instantly
    const upgrade = await upgradeSubscription('sub_123', 'prod_pro_plan');
    console.log('Upgrade status:', upgrade.status);
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    def upgrade_subscription(
        subscription_id: str,
        new_product_id: str,
        proration_mode: str = "difference_immediately"
    ):
        result = client.subscriptions.change_plan(
            subscription_id=subscription_id,
            product_id=new_product_id,
            quantity=1,
            proration_billing_mode=proration_mode
        )
        
        return {
            "status": result.status,
            "subscription_id": result.subscription_id,
            "payment_id": result.payment_id,
            "invoice_id": result.invoice_id
        }

    # Upgrade from Basic ($30) to Pro ($80)
    # With difference_immediately: charges $50 instantly
    upgrade = upgrade_subscription("sub_123", "prod_pro_plan")
    print(f"Upgrade status: {upgrade['status']}")
    ```
  </Tab>

  <Tab title="Go">
    ```go theme={null}
    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")
    ```
  </Tab>
</Tabs>

### Prorateringslägen

Välj hur kunder debiteras vid uppgradering:

| Läge                     | Beteende                                                                  | Bäst för                        |
| ------------------------ | ------------------------------------------------------------------------- | ------------------------------- |
| `difference_immediately` | Debiterar prisdifferensen omedelbart ($30→$80 = \$50)                     | Enkla uppgraderingar            |
| `prorated_immediately`   | Debiterar baserat på återstående tid i faktureringscykeln                 | Rättvis tidsbaserad fakturering |
| `full_immediately`       | Debiterar hela priset för det nya abonnemanget, ignorerar återstående tid | Faktureringscykeln återställs   |

<Tip>
  Använd `difference_immediately` för tydliga uppgraderingsflöden. Använd `prorated_immediately` när du vill ta hänsyn till oanvänd tid på det nuvarande abonnemanget.
</Tip>

## Korsförsäljning

Lägg till kompletterande produkter för befintliga kunder utan att behöva ange betalningsuppgifter igen.

```mermaid theme={null}
flowchart LR
    A[View Product] --> B{Existing Customer?}
    B -->|Yes| C[One-Click Buy] --> E[Deliver]
    B -->|No| D[Standard Checkout] --> E
```

### Implementering

<Tabs>
  <Tab title="TypeScript">
    ```typescript theme={null}
    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_id: customerId,
        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.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[0].payment_method_id, relatedProductId);
    }
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    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_id=customer_id,
            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:
            # 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[0].payment_method_id, related_product_id)
    ```
  </Tab>

  <Tab title="Go">
    ```go theme={null}
    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)
    }
    ```
  </Tab>
</Tabs>

## Nedgradering av abonnemang

När kunder vill flytta till en plan av lägre nivå, hantera övergången smidigt med automatisk kredit.

### Hur nedgraderingar fungerar

1. Kunder begär nedgradering (Pro → Basic)
2. Systemet beräknar återstående värde på aktuell plan
3. Kredit läggs till abonnemanget för framtida förnyelser
4. Kunden flyttas omedelbart till den nya planen

<Tabs>
  <Tab title="TypeScript">
    ```typescript theme={null}
    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.credit_amount);

      // Execute the downgrade
      const result = await client.subscriptions.changePlan(subscriptionId, {
        product_id: newProductId,
        quantity: 1,
        proration_billing_mode: 'difference_immediately'
      });

      // Credits are automatically applied to future renewals
      return result;
    }

    // Downgrade from Pro ($80) to Basic ($30)
    // $50 credit added to subscription, auto-applied on next renewal
    const downgrade = await downgradeSubscription('sub_123', 'prod_basic_plan');
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    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.credit_amount}")
        
        # Execute the downgrade
        result = 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 result

    # Downgrade from Pro ($80) to Basic ($30)
    # $50 credit added to subscription, auto-applied on next renewal
    downgrade = downgrade_subscription("sub_123", "prod_basic_plan")
    ```
  </Tab>

  <Tab title="Go">
    ```go theme={null}
    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),
                },
            },
        )
    }
    ```
  </Tab>
</Tabs>

<Info>
  Krediter från nedgraderingar som använder `difference_immediately` är prenumerationsbundna och tillämpas automatiskt på framtida förnyelser. De skiljer sig från [Credit-Based Billing](/features/credit-based-billing)-rättigheter.
</Info>

## Fullständigt exempel: Uppersell-flöde efter köp

Här är en komplett implementering som visar hur man erbjuder en uppersell efter ett lyckat köp:

<Tabs>
  <Tab title="TypeScript">
    ```typescript theme={null}
    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.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.currency
        },
        paymentMethodId: methods[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[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);
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    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:
            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.currency
            },
            'payment_method_id': methods[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[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)
    ```
  </Tab>
</Tabs>

## Bästa metoder

<AccordionGroup>
  <Accordion title="Time Your Upsells Strategically">
    Den bästa tiden att erbjuda en upsell är omedelbart efter ett lyckat köp när kunderna är i köpläge. Andra effektiva tillfällen:

    * Efter användningsmilstolpar för funktioner
    * När abonnemangsgränser närmar sig
    * Vid avslutning av onboarding
  </Accordion>

  <Accordion title="Validate Payment Method Eligibility">
    Innan du försöker ta ut en avgift med ett klick, verifiera betalningsmetoden:

    * Är kompatibel med produktens valuta
    * Har inte gått ut
    * Tillhör kunden

    API:t kommer att validera detta, men att kontrollera proaktivt förbättrar användarupplevelsen.
  </Accordion>

  <Accordion title="Handle Failures Gracefully">
    När avgifter med ett klick misslyckas:

    1. Gå tillbaka till standardkassaflödet
    2. Meddela kunden med tydlig kommunikation
    3. Erbjud att uppdatera betalningsmetoden
    4. Försök inte upprepade gånger misslyckade avgifter
  </Accordion>

  <Accordion title="Provide Clear Value Proposition">
    Upsells konverterar bättre när kunder förstår värdet:

    * Visa vad de får jämfört med nuvarande abonnemang
    * Lyft fram prisdifferensen, inte totalpriset
    * Använd socialt bevis (t.ex. "Mest populära uppgradering")
  </Accordion>

  <Accordion title="Respect Customer Choice">
    * Ge alltid ett enkelt sätt att tacka nej
    * Visa inte samma upsell upprepade gånger efter att den avvisats
    * Spåra och analysera vilka upsells som konverterar för att optimera erbjudandena
  </Accordion>
</AccordionGroup>

## Webhooks för övervakning

Spåra dessa webhook-händelser för uppersell och nedgradering flöden:

| Händelse                    | Utlösare                                             | Åtgärd                                      |
| --------------------------- | ---------------------------------------------------- | ------------------------------------------- |
| `payment.succeeded`         | Betalning för upsell/cross-sell slutförd             | Leverera produkt, uppdatera åtkomst         |
| `payment.failed`            | Avgift med ett klick misslyckades                    | Visa fel, erbjud försök igen eller fallback |
| `subscription.plan_changed` | Uppgradering/nedgradering slutförd                   | Uppdatera funktioner, skicka bekräftelse    |
| `subscription.active`       | Prenumeration återaktiverad efter abonnemangsändring | Ge åtkomst till ny nivå                     |

<Card title="Webhook Integration Guide" icon="webhook" href="/developer-resources/webhooks">
  Lär dig hur du ställer in och verifierar webhookslutpunkter.
</Card>

## Relaterade resurser

<CardGroup cols={2}>
  <Card title="Subscription Upgrade Guide" icon="arrows-rotate" href="/developer-resources/subscription-upgrade-downgrade">
    Detaljerad guide om abonnemangsändringar, proration-lägen och hantering av fel.
  </Card>

  <Card title="Checkout Sessions" icon="cart-shopping" href="/developer-resources/checkout-session">
    Komplett referens för att skapa kassasessioner med alla alternativ.
  </Card>

  <Card title="Customer Payment Methods API" icon="credit-card" href="/api-reference/customers/get-customer-payment-methods">
    API-referens för att lista kundbetalningsmetoder.
  </Card>

  <Card title="Add-ons" icon="puzzle-piece" href="/features/addons">
    Förbättra prenumerationer med flexibla tillägg för extra intäkter.
  </Card>
</CardGroup>
