快速导航×

Stripe Checkout 会话中集成自定义税率与折扣2025-12-13 09:44:13

stripe checkout 会话中集成自定义税率与折扣

本文旨在提供一个全面的教程,指导开发者如何在Stripe Checkout会话中正确集成自定义税率和折扣。我们将深入探讨Stripe API中`TaxRate`和`Coupon`对象的创建与应用,纠正常见的参数使用错误,特别是`discounts`参数的错误用法,并提供一个结构化的Python代码示例,帮助您高效、准确地处理支付环节中的税务和优惠逻辑。

概述:Stripe Checkout中的税务与折扣处理

Stripe Checkout是一个预构建的、托管的支付页面,用于简化支付流程。在许多业务场景中,我们需要在Checkout会话中应用自定义的税率和折扣,以满足不同地区税务要求或营销活动需求。Stripe API提供了灵活的机制来管理这些元素,主要通过TaxRate对象和Coupon或PromotionCode对象来实现。

正确地将这些对象集成到stripe.checkout.Session.create调用中是关键。常见的错误包括参数名称或结构不正确,导致API返回InvalidRequestError。本教程将详细介绍如何避免这些问题,并提供一个实用的代码示例。

关键概念

在深入代码之前,理解以下Stripe对象至关重要:

  • Stripe Checkout Session: Stripe提供的一个托管支付页面实例,通过调用stripe.checkout.Session.create创建。
  • TaxRate (税率): 代表一个具体的税率,可以包含显示名称、百分比、描述、管辖区等信息。税率可以设置为包含或不包含在价格中(inclusive/exclusive)。
  • Coupon (优惠券): 代表一个折扣,可以是固定金额折扣(amount_off)或百分比折扣(percent_off)。优惠券通常与一个优惠活动相关联。
  • PromotionCode (促销码): 允许客户在Checkout页面输入代码来应用折扣。一个促销码可以关联一个或多个优惠券。

集成自定义税率

Stripe允许您在Checkout会话中应用一个或多个税率。这些税率可以是预先在Stripe Dashboard中创建的,也可以通过API动态创建。通常,为了效率和管理,建议预先创建常用税率。

1. 创建或获取TaxRate对象

如果您需要动态创建税率,可以使用stripe.TaxRate.create()。如果税率是固定的,可以直接使用其ID。

import stripe

# 示例:动态创建税率(通常只在税率变动或首次设置时执行)
def create_or_get_tax_rate(display_name, percentage, jurisdiction, inclusive=False):
    # 检查是否已存在同名/同参数的税率,避免重复创建
    # 实际生产中,更好的做法是查询现有税率或从数据库加载预设ID
    try:
        # 尝试检索现有税率,这里简化处理,实际可能需要更复杂的查询逻辑
        # 例如,通过metadata存储自定义ID,或遍历检索
        # 为了演示,我们假设每次都创建,但建议在生产环境中避免
        tax_rate = stripe.TaxRate.create(
            display_name=display_name,
            description=f"{display_name} Tax",
            percentage=percentage,
            jurisdiction=jurisdiction,
            inclusive=inclusive,
        )
        return tax_rate.id
    except stripe.error.StripeError as e:
        print(f"Error creating tax rate: {e}")
        # 如果是已存在错误,可以尝试检索
        # 简化处理,直接返回None或抛出异常
        return None

# 假设您的订单模型中存储了税率信息
# order.tax.all() 返回一个包含税率信息的集合
# For example:
# class Tax(models.Model):
#     name = models.CharField(max_length=100)
#     rate = models.DecimalField(max_digits=5, decimal_places=2) # e.g., 5.00 for 5%
#     jurisdiction = models.CharField(max_length=100, default="US")

2. 将TaxRate应用于Checkout Session

税率可以应用于整个Checkout Session,也可以应用于会话中的单个line_item。当应用于整个会话时,Stripe会将这些税率应用于所有商品行。

在stripe.checkout.Session.create中,通过tax_rates参数传递一个税率ID列表:

Figma Figma

Figma 是一款基于云端的 UI 设计工具,可以在线进行产品原型、设计、评审、交付等工作。

Figma 1371 查看详情 Figma
# ... (在您的视图或服务函数中)
tax_rate_ids = []
for tax_obj in order.tax.all(): # 假设 order.tax.all() 返回您的自定义税率对象
    # 在生产环境中,您应该从数据库加载预先创建的Stripe TaxRate ID
    # 这里为了演示,我们假设 tax_obj 包含创建Stripe TaxRate所需的信息
    # 并且我们动态创建或获取其ID
    tax_rate_id = create_or_get_tax_rate(
        display_name=tax_obj.name,
        percentage=float(tax_obj.rate), # 确保是浮点数
        jurisdiction=tax_obj.jurisdiction,
        inclusive=False # 根据您的业务逻辑设置
    )
    if tax_rate_id:
        tax_rate_ids.append(tax_rate_id)

# ... 在 stripe.checkout.Session.create 中使用
# session = stripe.checkout.Session.create(
#     # ... 其他参数
#     tax_rates=tax_rate_ids,
#     # ...
# )

集成折扣

折扣可以通过Stripe的Coupon或PromotionCode实现。Coupon定义了折扣的具体内容(如减免金额或百分比),而PromotionCode则允许客户通过输入代码来应用这些优惠券。

1. 创建或获取Coupon对象

与税率类似,优惠券也可以通过API动态创建或预先创建。

# 示例:动态创建优惠券(通常只在优惠活动设置时执行)
def create_or_get_coupon(name, amount_off_cents=None, percent_off=None, currency='usd', duration='once'):
    try:
        if amount_off_cents:
            coupon = stripe.Coupon.create(
                amount_off=amount_off_cents, # 以最小货币单位表示,例如美分
                duration=duration, # once, forever, or repeating
                currency=currency,
                name=name,
            )
        elif percent_off:
            coupon = stripe.Coupon.create(
                percent_off=percent_off,
                duration=duration,
                currency=currency, # 即使是百分比折扣,也需要货币类型
                name=name,
            )
        else:
            raise ValueError("Must provide either amount_off_cents or percent_off")
        return coupon.id
    except stripe.error.StripeError as e:
        print(f"Error creating coupon: {e}")
        return None

# 假设您的订单模型中存储了折扣信息
# order.discount.all() 返回一个包含折扣信息的集合
# For example:
# class Discount(models.Model):
#     name = models.CharField(max_length=100)
#     amount = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True) # e.g., 10.00 for $10
#     percentage = models.DecimalField(max_digits=5, decimal_places=2, null=True, blank=True) # e.g., 10.00 for 10%

2. 将折扣应用于Checkout Session

在stripe.checkout.Session.create中,通过discounts参数传递一个包含优惠券ID或促销码ID的列表。请注意,这里的参数结构非常重要,也是原始问题中出错的地方。

正确的discounts参数应该是一个字典列表,每个字典包含一个coupon键或一个promotion_code键。

# ... (在您的视图或服务函数中)
discount_items = []
for discount_obj in order.discount.all(): # 假设 order.discount.all() 返回您的自定义折扣对象
    # 在生产环境中,您应该从数据库加载预先创建的Stripe Coupon ID
    # 这里为了演示,我们假设 discount_obj 包含创建Stripe Coupon所需的信息
    # 并且我们动态创建或获取其ID
    coupon_id = None
    if discount_obj.amount:
        coupon_id = create_or_get_coupon(
            name=discount_obj.name,
            amount_off_cents=int(float(discount_obj.amount) * 100), # 转换为美分
            currency='usd' # 根据您的货币设置
        )
    elif discount_obj.percentage:
        coupon_id = create_or_get_coupon(
            name=discount_obj.name,
            percent_off=float(discount_obj.percentage),
            currency='usd' # 即使是百分比折扣,也需要货币类型
        )

    if coupon_id:
        discount_items.append({"coupon": coupon_id}) # 正确的参数结构!

# ... 在 stripe.checkout.Session.create 中使用
# session = stripe.checkout.Session.create(
#     # ... 其他参数
#     discounts=discount_items,
#     # ...
# )

原始错误分析: 原始代码中使用了discounts=[{"discounts": '{{COUPON_ID}}'}]。Stripe API期望的是discounts列表中的每个元素是一个字典,该字典直接包含"coupon"或"promotion_code"键,而不是再次嵌套一个"discounts"键。因此,Received unknown parameter: discounts[0][discounts]这个错误提示非常准确。

完整代码示例

以下是一个结合了税率和折扣的Django视图示例,基于您提供的原始代码进行修正和优化。

import stripe
from django.conf import settings
from django.http import JsonResponse
from django.views import View
# from .models import Order, Tax, Discount # 假设您有这些模型

# 配置Stripe API密钥
stripe.api_key = settings.STRIPE_SECRET_KEY # 确保在settings.py中配置

# 辅助函数:创建或获取Stripe TaxRate
def create_or_get_stripe_tax_rate(display_name, percentage, jurisdiction, inclusive=False):
    """
    创建一个Stripe TaxRate或返回现有TaxRate的ID。
    在生产环境中,建议预先创建TaxRate并存储其ID,而不是每次都动态创建。
    """
    # 实际应用中,您可能会查询数据库中存储的Stripe TaxRate ID
    # 这里为演示目的,简化为直接创建
    try:
        tax_rate = stripe.TaxRate.create(
            display_name=display_name,
            description=f"{display_name} Tax",
            percentage=percentage,
            jurisdiction=jurisdiction,
            inclusive=inclusive,
        )
        return tax_rate.id
    except stripe.error.StripeError as e:
        # 更健壮的错误处理,例如记录日志
        print(f"Error creating Stripe TaxRate: {e}")
        return None

# 辅助函数:创建或获取Stripe Coupon
def create_or_get_stripe_coupon(name, amount_off_cents=None, percent_off=None, currency='usd', duration='once'):
    """
    创建一个Stripe Coupon或返回现有Coupon的ID。
    在生产环境中,建议预先创建Coupon并存储其ID,而不是每次都动态创建。
    """
    try:
        if amount_off_cents is not None:
            coupon = stripe.Coupon.create(
                amount_off=amount_off_cents,
                duration=duration,
                currency=currency,
                name=name,
            )
        elif percent_off is not None:
            coupon = stripe.Coupon.create(
                percent_off=percent_off,
                duration=duration,
                currency=currency,
                name=name,
            )
        else:
            raise ValueError("Must provide either amount_off_cents or percent_off")
        return coupon.id
    except stripe.error.StripeError as e:
        print(f"Error creating Stripe Coupon: {e}")
        return None

class CreateCheckoutSessionOrderView(View):
    def get(self, request, *args, **kwargs):
        order_id = self.kwargs["order_id"]
        DOMAIN: str = 'http://127.0.0.1:8000' # 生产环境应使用您的实际域名

        # 假设 Order, Tax, Discount 模型已定义并可访问
        # from your_app.models import Order, Tax, Discount
        try:
            # 替换为您的实际订单获取逻辑
            # order = Order.objects.get(id=order_id)
            # 模拟订单数据
            class MockTax:
                def __init__(self, name, rate, jurisdiction):
                    self.name = name
                    self.rate = rate
                    self.jurisdiction = jurisdiction
            class MockDiscount:
                def __init__(self, name, amount=None, percentage=None):
                    self.name = name
                    self.amount = amount
                    self.percentage = percentage
            class MockOrder:
                def __init__(self, id, total_cost, name, taxes, discounts):
                    self.id = id
                    self._total_cost = total_cost
                    self._name = name
                    self._taxes = taxes
                    self._discounts = discounts
                def get_total_cost(self):
                    return self._total_cost
                def __str__(self):
                    return self._name
                def tax(self):
                    # 模拟ManyToManyField
                    class TaxManager:
                        def all(self):
                            return self._taxes
                    return TaxManager()
                def discount(self):
                    # 模拟ManyToManyField
                    class DiscountManager:
                        def all(self):
                            return self._discounts
                    return DiscountManager()

            # 模拟订单数据
            order = MockOrder(
                id=order_id,
                total_cost=100.00, # 示例总价
                name=f"Order #{order_id}",
                taxes=[
                    MockTax(name="Sales Tax", rate=7.5, jurisdiction="US"),
                    # MockTax(name="VAT", rate=20.0, jurisdiction="GB"),
                ],
                discounts=[
                    MockDiscount(name="Welcome Discount", amount=10.00), # 10美元折扣
                    # MockDiscount(name="Promo 5%", percentage=5.0), # 5%折扣
                ]
            )


        except Exception as e:
            return JsonResponse({'error': str(e)}, status=404)

        # --- 处理税率 ---
        tax_rate_ids = []
        for tax_obj in order.tax().all():
            tax_rate_id = create_or_get_stripe_tax_rate(
                display_name=tax_obj.name,
                percentage=float(tax_obj.rate),
                jurisdiction=tax_obj.jurisdiction,
                inclusive=False, # 根据您的业务逻辑调整
            )
            if tax_rate_id:
                tax_rate_ids.append(tax_rate_id)

        # --- 处理折扣 ---
        discount_items = []
        for discount_obj in order.discount().all():
            coupon_id = None
            if discount_obj.amount is not None:
                coupon_id = create_or_get_stripe_coupon(
                    name=discount_obj.name,
                    amount_off_cents=int(float(discount_obj.amount) * 100),
                    currency='usd',
                )
            elif discount_obj.percentage is not None:
                coupon_id = create_or_get_stripe_coupon(
                    name=discount_obj.name,
                    percent_off=float(discount_obj.percentage),
                    currency='usd', # 即使是百分比折扣,也需要货币类型
                )

            if coupon_id:
                discount_items.append({"coupon": coupon_id}) # 正确的参数结构

        try:
            session = stripe.checkout.Session.create(
                payment_method_types=['card'],
                line_items=[
                    {
                        'price_data': {
                            'currency': 'usd',
                            'unit_amount': int(order.get_total_cost() * 100), # 总价转换为美分
                            'product_data': {
                                'name': order.__str__(),
                                'description': f"Purchase for order {order.id}",
                            },
                        },
                        'quantity': 1,
                        # 如果需要对特定商品行应用税率,可以在这里添加 'tax_rates': [...]
                        # 但通常在会话级别应用更常见
                    },
                ],
                payment_intent_data={
                    'metadata': {
                        'order_id': order.id,
                    },
                },
                mode='payment',
                success_url=DOMAIN + '/success/',
                cancel_url=DOMAIN + '/cancel/',
                tax_rates=tax_rate_ids,      # 应用税率
                discounts=discount_items,    # 应用折扣
            )
            return JsonResponse({'id': session.id})
        except stripe.error.StripeError as e:
            # 捕获Stripe API错误
            return JsonResponse({'error': str(e)}, status=500)
        except Exception as e:
            # 捕获其他未知错误
            return JsonResponse({'error': f"An unexpected error occurred: {e}"}, status=500)

注意事项与最佳实践

  1. Stripe API Key: 确保您的Stripe secret key已正确配置在Django settings.py 中,并被stripe.api_key引用。
  2. TaxRate 和 Coupon 的生命周期: 在生产环境中,TaxRate和Coupon对象通常是一次性创建的(例如,在Stripe Dashboard中,或通过管理界面/脚本)。然后,您只需在数据库中存储它们的Stripe ID,并在创建Checkout Session时引用这些ID。动态创建(如示例中所示)虽然可行,但在每次用户结账时都调用Stripe API创建新对象会增加延迟和不必要的API请求。
  3. 错误处理: 务必在API调用周围添加try-except块来捕获stripe.error.StripeError,以便优雅地处理网络问题、无效参数或其他Stripe API返回的错误。
  4. 货币单位: Stripe API处理金额时通常使用最小货币单位(例如,美元使用美分)。确保在传递unit_amount和amount_off时进行正确的转换(乘以100)。
  5. **税率的包含性 (

以上就是Stripe Checkout 会话中集成自定义税率与折扣的详细内容,更多请关注其它相关文章!


# 东莞樟木头网站建设  # 转换为  # 加载  # 可以通过  # 即使是  # 每次都  # 提供一个  # 青岛网站建设详细教程  # 衡水网站快照快速优化  # 是一个  # 西藏seo快排如何引流  # 在线seo外链发布工具  # 双城网站建设地址  # 济宁品牌网站优化公司  # seo综合查询乱码  # 户外活动推广营销方案  # 天津软文营销推广  # python  # 应用于  # 自定义  # 您的  #   # 网络问题  # cos  # api调用  # django  # ai  # session  # app  # go  # json  # git  # js 


相关栏目: 【 企业资讯168 】 【 行业动态20933 】 【 网络营销52431 】 【 网络学院91036 】 【 运营推广7012 】 【 科技资讯60970


相关推荐: windows10怎么关闭系统提示音_windows10彻底静音设置方法  sublime如何优雅地处理行尾空格_sublime自动清理多余空白字符配置  Win11怎么设置开机NumLock亮 Win11修改注册表InitialKeyboardIndicators值  Golang如何优雅处理error_Golang error处理最佳实践总结  AO3网页版合集入口 Archive of Our Own同人作品浏览指南  QQ邮箱登录首页官网地址2026 QQ邮箱官方网页入口  理解J*aScript Promise的微任务队列与执行顺序  网易大神怎么保存别人动态的图片_网易大神动态图片保存方法  2026春节假期票务安排_2026春节放假购票指南  PySpark中高效提取字符串右侧可变长度数字:使用regexp_extract  Excel中VLOOKUP的第四个参数是干什么用的_Excel VLOOKUP第四参数作用解析  c++中的std::forward_list和std::list有什么不同_c++ forward_list与list区别分析  Composer的 "licenses" 命令如何帮助你遵守开源协议_检查项目依赖的许可证合规性  Go语言中的*string:深入理解字符串指针  Golang如何测试channel通信行为_Golang channel通信测试与分析方法  QQ邮箱官网登录入口 QQ邮箱网页版邮箱快速登录  c++如何使用TBB库进行任务并行_c++ Intel线程构建模块  在命令行怎么运行html项目_命令行运行html项目方法【教程】  MAC的“快捷指令”怎么同步到iPhone_MAC利用iCloud同步所有设备的自动化指令  steam官方入口大全 steam账号注册及操作指南  铁路12306卧铺选择攻略 铁路12306下铺座位预定技巧  网页是怎么运行的HTML是什么_释网页运行与HTML概念【解析】  c++如何编写一个动态链接库(DLL/SO)_c++模块化编程与接口导出  微博网页版怎么开启两步验证_微博网页版账号安全两步验证设置方法  百度浏览器字体显示异常偏小_百度浏览器字体渲染修复方案  深入理解J*aScript Promise异步执行与微任务队列  漫蛙Manwa2官网入口地址分享 漫蛙漫画PC版永久访问通道  Go调试环境为何无法启动_Go调试器启动失败原因与解决策略  包子漫画官方网站在线链接-包子漫画在线阅读平台主页地址  2026年CSGO开箱网站推荐 CSGO开箱平台精选  怎样使用“本地安全策略”提升Windows安全性_Secpol.msc配置指南【高手】  Golang如何实现微服务鉴权与权限控制_Golang微服务鉴权与权限管理实践  Python实时数据流中的动态最值查找策略  4399网页游戏电脑版全新入口 4399电脑端在线玩指南  Python Socket多播通信中指定源IP地址的实践指南  蛙漫2台版漫画地址 Manwa2正版网页版链接  俄罗斯Yandex搜索引擎入口_Yandex官网免登录一键访问  优化LangChain文档加载与ChromaDB集成:解决多文档处理与分块问题  Golang如何优化内存分配与垃圾回收_Golang内存管理与GC优化实践  微信网页版官方入口教程 微信网页版网页版快速登录步骤  c++中的std::launder有什么实际用途_c++对象生命周期与指针优化  PHP 枚举:根据字符串获取枚举案例的策略与实现  vivo云服务网页版登录 怎么登录vivo云服务网页版  Python多线程中正确使用sigwait处理SIGALRM信号  小米14应用无法联网原因分析_小米14网络权限修复  在React函数组件中利用原生HTML5进行邮箱地址验证  火锅吃太多会怎样 火锅吃太多会上火吗  理解Python模块与全局变量的作用域管理  护手霜蹭到袖口上了如何清洗? 怎样避免留下一圈油印?  高德地图公交到站提醒失败如何解决 高德提醒权限设置