【发布时间】:2020-04-13 18:23:31
【问题描述】:
更新:我实施了部分解决方案,并在下面修改了这篇文章。现在正在正确的 Stripe 帐户中创建客户对象。但是,仍然存在次要问题,即 Stripe Card obj 未在 Stripe Customer obj 上保存(更新)(但以前是)。
我们有一个 Django 项目需要使用两个不同的 Stripe 帐户(出于合规性原因)。一个 Stripe 帐户(“SA1”)用于 SaaS 计费,我们的第二个 Stripe 帐户(“SA2”)使用 Stripe Connect 处理特定的一次性付款。
设置完成后,我开始看到意外的行为,即请求在两个帐户之间拆分发送,而不是转到预期的 SA1 帐户。一些 API 请求被发送到 SA1(我们想要的),一些 API 请求被发送到 SA2(我们不想要的)。我会进一步解释:
我们有一个 admin_billing 视图,新客户保存他们的卡以创建和保存新的 Stripe 客户及其卡。
def admin_billing(request):
"""
Query customer table and adds a new card.
:param request:
:return: Billing rendering with template
"""
form = StripeAddCardForm(request.POST or None)
if form.is_valid():
customer = Customer.objects.get_or_create_for_user(request.user)
token = form.cleaned_data['stripeToken']
card = Card(customer=customer, stripe_card_id=token)
try:
card.save()
except stripe.error.CardError as e:
body = e.json_body
err = body.get('error', {})
messages.error(request, err.get('message'))
log.error("Stripe card error: %s" % (e))
except stripe.error.StripeError as e:
messages.error(request, 'Please try again or report the problem')
log.error("Stripe error: %s" % (e))
except Exception as e:
messages.error(request, 'Please try again or report the problem')
log.error("Error while handling stripe: %s" % (e))
finally:
return redirect(reverse('admin-billing'))
context = {
'form': form,
'stripe_pub_key': settings.STRIPE_LIVE_PUBLIC_KEY
}
return render(request, 'management/billing.html', context)
StripeAddCardForm 是一个 Django 表单:
class StripeAddCardForm(forms.Form):
stripeToken = forms.CharField()
我还可以确认STRIPE_LIVE_PUBLIC_KEY 是我们 SA1 帐户的正确公钥。
在billing.models 我们有:
from stripe import Customer as StripeCustomer, Subscription as StripeSubscription, Charge
from jsonfield import JSONField
class CustomerManager(models.Manager):
def get_or_create_for_user(self, user):
try:
customer = user.customer
return customer
except AttributeError:
pass
stripe_customer = StripeCustomer.create(
email=user.email,
description=user.username
)
customer = Customer.objects.create(
user=user,
stripe_customer_id=stripe_customer.id,
stripe_customer_data=stripe_customer,
)
return customer
class Customer(models.Model):
user = models.OneToOneField('users.User', null=True, on_delete=models.SET_NULL, related_name='customer')
stripe_customer_id = models.CharField(unique=True, max_length=255)
stripe_customer_data = JSONField(blank=True, default=dict, editable=False)
objects = CustomerManager()
def __str__(self):
return self.stripe_customer_id
@property
def stripe_customer(self):
return StripeCustomer.retrieve(self.stripe_customer_id)
class Card(models.Model):
STATUS_ACTIVE = 'active'
STATUS_CANCELED = 'canceled'
STATUS = (
(STATUS_ACTIVE, 'Card active'),
(STATUS_CANCELED, 'Card canceled'),
)
customer = models.ForeignKey('Customer', on_delete=models.PROTECT, related_name='cards')
stripe_card_id = models.CharField(unique=True, max_length=255)
stripe_card_data = JSONField(default=dict)
is_primary = models.BooleanField(default=False)
status = models.CharField(choices=STATUS, max_length=20, default=STATUS_ACTIVE)
date_created = models.DateTimeField(auto_now_add=True)
date_canceled = models.DateTimeField(null=True)
objects = CardQuerySet.as_manager()
class Meta:
ordering = ['id']
def __str__(self):
return self.stripe_card_id
@property
def stripe_customer(self):
if not self.customer:
return None
return self.customer.stripe_customer
def save(self, *args, **kwargs):
# tagging existing card as primary (only one card can be primary card)
if self.pk and self.is_primary:
if self.status == self.STATUS_CANCELED:
raise ValueError("Primary card must be active")
self.customer.cards.all().exclude(id=self.id).filter(is_primary=True).update(is_primary=False)
self.customer.stripe_customer_data = StripeCustomer.modify(
self.customer.stripe_customer_id,
default_source=self.stripe_card_id,
)
self.customer.save()
# new card
if not self.pk:
self.stripe_card_data = self.stripe_customer.sources.create(source=self.stripe_card_id)
self.stripe_card_id = self.stripe_card_data['id']
# if this is a first card for this customer
if not self.customer.cards.get_active():
self.is_primary = True
self.customer.stripe_customer_data = StripeCustomer.modify(
self.customer.stripe_customer_id,
default_source=self.stripe_card_id,
)
self.customer.save()
super(Card, self).save(*args, **kwargs)
然后我们在视图/模板中使用 stripe.js 来处理表单:
<script src="https://js.stripe.com/v3/"></script>
<script>
// Create a Stripe client. This is SA1 key passed in from view ctx
var stripe = Stripe('{{ stripe_pub_key }}');
// Create an instance of Elements.
var elements = stripe.elements();
// Create an instance of the card Element.
var card = elements.create('card', {style: style});
// Add an instance of the card Element into the `card-element` <div>.
card.mount('#card-element');
// Handle real-time validation errors from the card Element.
card.addEventListener('change', function(event) {
var displayError = document.getElementById('card-errors');
if (event.error) {
displayError.textContent = event.error.message;
} else {
displayError.textContent = '';
}
});
// Handle form submission.
var stripeCardForm = document.getElementById('payment-form');
stripeCardForm.addEventListener('submit', function(event) {
event.preventDefault();
stripe.createToken(card).then(function(result) {
if (result.error) {
// Inform the user if there was an error.
var errorElement = document.getElementById('card-errors');
errorElement.textContent = result.error.message;
} else {
// Send the token to your server.
stripeTokenHandler(result.token);
}
});
});
// Submit the form with the token ID.
function stripeTokenHandler(token) {
// Insert the token ID into the form so it gets submitted to the server
var form = document.getElementById('payment-form');
var hiddenInput = document.createElement('input');
hiddenInput.setAttribute('type', 'hidden');
hiddenInput.setAttribute('name', 'stripeToken');
hiddenInput.setAttribute('value', token.id);
form.appendChild(hiddenInput);
// Submit the form
form.submit();
}
</script>
在表单保存时,会发生什么:
-
POST到/v1/tokens的请求已成功发送到 SA1(良好/预期) - 卡实例保存到数据库(良好/预期)
-
POSTreq to/v1/customers成功发送到SA2(坏/???) -
客户实例保存到数据库(良好/预期)
{ “错误”: { “代码”:“resource_missing”, "doc_url": "https://stripe.com/docs/error-codes/resource-missing", "message": "没有这样的令牌:{{ token }}", “参数”:“来源”, “类型”:“invalid_request_error” } }
应用程序登录表单提交:
2019-12-21T18:14:52.154021+00:00: at=info method=POST path="/manage/billing/" host=app.com
2019-12-21T18:14:52.134957+00:00: [4] [INFO] pathname=/app/python/lib/python3.6/site-packages/stripe/util.py lineno=63 funcname=log_info message='Stripe API response' path=https://api.stripe.com/v1/customers/cus_foo response_code=404
2019-12-21T18:14:52.136422+00:00: [4] [INFO] pathname=/app/python/lib/python3.6/site-packages/stripe/util.py lineno=63 funcname=log_info error_code=resource_missing error_message='No such customer: cus_foo' error_param=id error_type=invalid_request_error message='Stripe API error received'
2019-12-21T18:14:52.136791+00:00: [4] [ERROR] pathname=./management/views.py lineno=1755 funcname=admin_billing Stripe error: Request req_bar: No such customer: cus_foo
2019-12-21T18:14:52.153639+00:00: [4] [INFO] pathname=/app/python/lib/python3.6/site-packages/uvicorn/protocols/http/httptools_impl.py lineno=443 funcname=send ('10.45.113.223', 12377) - "POST /manage/billing/ HTTP/1.1" 302
我通过在 models.py 中的客户计费管理器模型中显式设置 StripeCustomer.create() 调用上的正确 SA1 密钥解决了 API 请求路由问题(请参阅下面的新行):
class CustomerManager(models.Manager):
def get_or_create_for_user(self, user):
try:
customer = user.customer
return customer
except AttributeError:
pass
stripe_customer = StripeCustomer.create(
email=user.email,
description=user.username,
# NEW LINE
**api_key=settings.STRIPE_LIVE_SECRET_KEY**
)
customer = Customer.objects.create(
user=user,
stripe_customer_id=stripe_customer.id,
stripe_customer_data=stripe_customer,
)
return customer
现在两个请求(包括/v1/customers)都路由到正确的 Stripe 帐户 SA1,我可以看到 Customer obj 正在创建。然而,一个 Card obj 也应该被保存并附加到那个 Stripe Customer,但不是。我可以在之前的 Stripe 日志中看到,在创建客户时会有 customer.updated 和 payment_method.attached 调用,目前还没有发生)。现在需要调试这个问题,我目前的假设是在我们的 Card 模型声明的 save() 方法中:
# new card
if not self.pk:
self.stripe_card_data = self.stripe_customer.sources.create(source=self.stripe_card_id)
self.stripe_card_id = self.stripe_card_data['id']
# if this is a first card for this customer
if not self.customer.cards.get_active():
self.is_primary = True
self.customer.stripe_customer_data = StripeCustomer.modify(
self.customer.stripe_customer_id,
default_source=self.stripe_card_id,
)
self.customer.save()
【问题讨论】:
-
您可能会混淆两个帐户的密钥。
-
我认为情况并非如此。我已经确认这个秘密也是正确的
-
您为某些请求使用了错误的 secret_key,或者您为某些请求使用了 Connect 并设置了
Stripe-Header(docs)。我建议写信给support.stripe.com/contact,Stripe 可以深入了解您的日志,以查看您发送给他们的确切内容。 -
谢谢。将进一步研究
Stripe-Header,看看它是否可以设置在/v1/customersreq 上。我们正在使用带有 SA2 的 Stripe Connect
标签: python django stripe-payments