0

0

Stripe Checkout 会话中集成自定义税率与折扣

心靈之曲

心靈之曲

发布时间:2025-12-13 09:44:13

|

709人浏览过

|

来源于php中文网

原创

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列表:

CreateWise AI
CreateWise AI

为播客创作者设计的AI创作工具,AI自动去口癖、提交亮点和生成Show notes、标题等

下载
# ... (在您的视图或服务函数中)
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. **税率的包含性 (

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

WorkBuddy
WorkBuddy

腾讯云推出的AI原生桌面智能体工作台

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
Python Web 框架 Django 深度开发
Python Web 框架 Django 深度开发

本专题系统讲解 Python Django 框架的核心功能与进阶开发技巧,包括 Django 项目结构、数据库模型与迁移、视图与模板渲染、表单与认证管理、RESTful API 开发、Django 中间件与缓存优化、部署与性能调优。通过实战案例,帮助学习者掌握 使用 Django 快速构建功能全面的 Web 应用与全栈开发能力。

166

2026.02.04

session失效的原因
session失效的原因

session失效的原因有会话超时、会话数量限制、会话完整性检查、服务器重启、浏览器或设备问题等等。详细介绍:1、会话超时:服务器为Session设置了一个默认的超时时间,当用户在一段时间内没有与服务器交互时,Session将自动失效;2、会话数量限制:服务器为每个用户的Session数量设置了一个限制,当用户创建的Session数量超过这个限制时,最新的会覆盖最早的等等。

336

2023.10.17

session失效解决方法
session失效解决方法

session失效通常是由于 session 的生存时间过期或者服务器关闭导致的。其解决办法:1、延长session的生存时间;2、使用持久化存储;3、使用cookie;4、异步更新session;5、使用会话管理中间件。

776

2023.10.18

cookie与session的区别
cookie与session的区别

本专题整合了cookie与session的区别和使用方法等相关内容,阅读专题下面的文章了解更详细的内容。

97

2025.08.19

scripterror怎么解决
scripterror怎么解决

scripterror的解决办法有检查语法、文件路径、检查网络连接、浏览器兼容性、使用try-catch语句、使用开发者工具进行调试、更新浏览器和JavaScript库或寻求专业帮助等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

492

2023.10.18

500error怎么解决
500error怎么解决

500error的解决办法有检查服务器日志、检查代码、检查服务器配置、更新软件版本、重新启动服务、调试代码和寻求帮助等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

382

2023.10.25

数据库三范式
数据库三范式

数据库三范式是一种设计规范,用于规范化关系型数据库中的数据结构,它通过消除冗余数据、提高数据库性能和数据一致性,提供了一种有效的数据库设计方法。本专题提供数据库三范式相关的文章、下载和课程。

389

2023.06.29

如何删除数据库
如何删除数据库

删除数据库是指在MySQL中完全移除一个数据库及其所包含的所有数据和结构,作用包括:1、释放存储空间;2、确保数据的安全性;3、提高数据库的整体性能,加速查询和操作的执行速度。尽管删除数据库具有一些好处,但在执行任何删除操作之前,务必谨慎操作,并备份重要的数据。删除数据库将永久性地删除所有相关数据和结构,无法回滚。

2111

2023.08.14

TypeScript类型系统进阶与大型前端项目实践
TypeScript类型系统进阶与大型前端项目实践

本专题围绕 TypeScript 在大型前端项目中的应用展开,深入讲解类型系统设计与工程化开发方法。内容包括泛型与高级类型、类型推断机制、声明文件编写、模块化结构设计以及代码规范管理。通过真实项目案例分析,帮助开发者构建类型安全、结构清晰、易维护的前端工程体系,提高团队协作效率与代码质量。

26

2026.03.13

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
最新Python教程 从入门到精通
最新Python教程 从入门到精通

共4课时 | 22.5万人学习

Django 教程
Django 教程

共28课时 | 5万人学习

SciPy 教程
SciPy 教程

共10课时 | 1.9万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号