0
votes

I try to use the new Stripe's PaymentIntent system to be ready when SCA will be launched in EU.

I only use one-time payment.

I succeed to make the payment with the PaymentIntent following Stripe's documentation. But I'm unable to create an invoice for every payment (I must have one according to the law), and I tried a lot of things.

But first, I think I need to show my code to introduce the troubles I have.

In my view, I create a Stripe Session :

public_token = settings.STRIPE_PUBLIC_KEY    
stripe.api_key = settings.STRIPE_PRIVATE_KEY

stripe_sesssion = stripe.checkout.Session.create(
    payment_method_types=['card'],
    line_items=[{
        'name':'My Product',
        'description': description,
        'amount': amount,
        'currency': 'eur',
        'quantity': 1,
    }],
    customer=customer_id,
    success_url=f'{settings.SITE_URL}/ok.html',
    cancel_url=f'{settings.SITE_URL}/payment_error.html',
)

Then, the user click on the "Purchase" button on my web page and is redirected to the Stripe's Checkout page.

enter image description here

After the user filled his payment card informations, Stripe call my Webhook (according to the checkout.session.completed event triggered).

Here's my webhook function code :

@csrf_exempt
def webhook_payment_demande(request):
    payload = request.body
    sig_header = request.META['HTTP_STRIPE_SIGNATURE']
    event = None
    if settings.DEBUG is False:
        endpoint_secret = "whsec_xxx"
    else:
        endpoint_secret = "whsec_xxx"

    try:
        event = stripe.Webhook.construct_event(
            payload, sig_header, endpoint_secret
        )
    except ValueError as e:
        # Invalid payload
        return HttpResponse(status=400)
    except stripe.error.SignatureVerificationError as e:
        # Invalid signature
        return HttpResponse(status=400)

    # Handle the event
    if event['type'] == 'checkout.session.completed':
        stripe_session = event['data']['object']

        invoice_item = stripe.InvoiceItem.create(
            customer=customer_id,
            amount=str(amount),
            currency="eur",
            description=description
        )

        invoice = stripe.Invoice.create(
            customer=customer_id,
        )

        invoice_payment = stripe.Invoice.pay(
            invoice,
            source=card
        )

        [...] Deliver product by e-mail and stuff [...]
  • If I execute that code, the payment is done a first time (PaymentIntent) but also a second time to finalize the invoice I create after. So my customer payed twice the amount.
  • If I remove the Invoice.pay function, Stripe will charge my client one hour after anyway using an existing payment card into Stripe.
  • If I don't create any invoice manually inside my Web hook function, Stripe doesn't make one automatically.
  • If I create the invoice into my first view, just right after the Stripe Checkout Session and before my customer fill his card informations, he will be charged for the amount even if he didn't finalize the payment (because he had a existing card into Stripe).

I'm reading the documentation for days and I've not found a good tutorial to make a one-time Payment with SCA compatibility and having a bill after that.

Is a nice person has already fixed his/her Stripe payment API system for SCA compliance and have found a way to deal with this ?

A lot of thanks for your help ! :)

2

2 Answers

0
votes

Your code is creating one-time charges via Checkout. What you are looking for is the email receipt feature as documented here https://stripe.com/docs/receipts

This lets you email your customer after a successful charge on your account which should act as an Invoice. You can also link to this email receipt directly by looking at the Charge's receipt_url property: https://stripe.com/docs/api/charges/object#charge_object-receipt_url

0
votes

Posting this as requested. Using .NET and React.js. Steps:

On load of the payment page, I call stripe to create an Intent from my API

var response = await _setupIntentService.CreateAsync(new SetupIntentCreateOptions
{
     PaymentMethodTypes = new List<string> { "card" },
     Usage = "off_session", // Allows card to be used later one
});
return response.ClientSecret;

Then using stripe elements, I render the card details form (self explanatory on the stripe docs website). On submission of this form, call stripe with the intent to create a payment method (React):

this.props.stripe.handleCardSetup(intent)
  .then((stripeResponse) => {
    submitSubscription(stripeResponse.setupIntent.payment_method);
  })

submitSubscription calls my API.

If the customer exists, I detach the existing payment method

_paymentMethodService.ListAsync(new PaymentMethodListOptions { Limit = 1, CustomerId = customerId, Type = "card" });
if (paymentMethods.Any())
{
   await _paymentMethodService.DetachAsync(paymentMethods.First().Id, new PaymentMethodDetachOptions());
}

And attach the new payment method, also setting the default payment for the customer

var options = new PaymentMethodAttachOptions { CustomerId = customerId };
await _paymentMethodService.AttachAsync(paymentMethodId, options);
await _customerService.UpdateAsync(customer.Id, new CustomerUpdateOptions
{
    InvoiceSettings = new CustomerInvoiceSettingsOptions { DefaultPaymentMethodId = paymentMethodId }
});

If the customer doesn't exist, create one with the payment method id

 _customerService.CreateAsync(new CustomerCreateOptions
{
    Email = email,
    PaymentMethodId = paymentMethodId,
    InvoiceSettings = new CustomerInvoiceSettingsOptions { DefaultPaymentMethodId = paymentMethodId }
});

I'm using plans and subscriptions which are well documented by Stripe but my main issue was I was using Source and payment methods together. Source was the old way if you are taking off session payments.

Hope that helps.