@@ -82,8 +82,10 @@ class StripeProviderV3(BasicProvider):
8282 :param use_token: Use instance.token instead of instance.pk in client_reference_id
8383 :param endpoint_secret: Endpoint Signing Secret.
8484 :param secure_endpoint: Validate the recieved data, useful for development.
85- :param recurring_payments: Enable wallet-based recurring payments (server-initiated).
86- :param store_payment_method: Store PaymentMethod for future use (auto-enabled if recurring_payments=True).
85+ :param recurring_payments: Enable wallet-based recurring payments
86+ (server-initiated).
87+ :param store_payment_method: Store PaymentMethod for future use
88+ (auto-enabled if recurring_payments=True).
8789 """
8890
8991 form_class = BasePaymentForm
@@ -154,16 +156,54 @@ def create_session(self, payment):
154156 if payment .billing_email and "customer" not in session_data :
155157 session_data .update ({"customer_email" : payment .billing_email })
156158
157- # Patch session with billing name
159+ # Pre-fill billing address in checkout form (only for new customers)
160+ # This makes Stripe's billing_address_collection="auto" seamless:
161+ # - If address is needed for tax, form is already filled
162+ # - If address is not needed, pre-fill is harmless
163+ if "customer" not in session_data :
164+ address_data = {}
165+ if payment .billing_address_1 :
166+ address_data ["line1" ] = payment .billing_address_1
167+ if payment .billing_address_2 :
168+ address_data ["line2" ] = payment .billing_address_2
169+ if payment .billing_city :
170+ address_data ["city" ] = payment .billing_city
171+ if payment .billing_postcode :
172+ address_data ["postal_code" ] = payment .billing_postcode
173+ if payment .billing_country_area :
174+ address_data ["state" ] = payment .billing_country_area
175+ if payment .billing_country_code :
176+ address_data ["country" ] = payment .billing_country_code
177+
178+ if address_data :
179+ session_data ["customer_details" ] = {"address" : address_data }
180+
181+ # Patch session with billing name and address in metadata
182+ # Note: We rely on Stripe's default billing_address_collection="auto"
183+ # which only collects address when needed for tax/compliance.
184+ # The metadata below is stored for audit trail regardless.
185+ metadata = {}
158186 if payment .billing_first_name or payment .billing_last_name :
159- session_data .update (
160- {
161- "metadata" : {
162- "customer_name" : f"{ payment .billing_first_name } "
163- f"{ payment .billing_last_name } "
164- }
165- }
187+ metadata ["customer_name" ] = (
188+ f"{ payment .billing_first_name } { payment .billing_last_name } " .strip ()
166189 )
190+ if payment .billing_address_1 :
191+ metadata ["billing_address_1" ] = payment .billing_address_1
192+ if payment .billing_address_2 :
193+ metadata ["billing_address_2" ] = payment .billing_address_2
194+ if payment .billing_city :
195+ metadata ["billing_city" ] = payment .billing_city
196+ if payment .billing_postcode :
197+ metadata ["billing_postcode" ] = payment .billing_postcode
198+ if payment .billing_country_code :
199+ metadata ["billing_country_code" ] = payment .billing_country_code
200+ if payment .billing_country_area :
201+ metadata ["billing_country_area" ] = payment .billing_country_area
202+ if payment .billing_phone :
203+ metadata ["billing_phone" ] = str (payment .billing_phone )
204+
205+ if metadata :
206+ session_data ["metadata" ] = metadata
167207 try :
168208 return stripe .checkout .Session .create (** session_data )
169209 except stripe .error .StripeError as e :
@@ -187,7 +227,7 @@ def refund(self, payment, amount=None) -> int:
187227 amount = self .convert_amount (payment .currency , to_refund ),
188228 reason = "requested_by_customer" ,
189229 )
190- except stripe .StripeError as e :
230+ except stripe .error . StripeError as e :
191231 raise PaymentError (e ) from e
192232 else :
193233 payment .attrs .refund = json .dumps (refund )
@@ -212,6 +252,10 @@ def get_line_items(self, payment) -> list:
212252 order_no = payment .token if self .use_token else payment .pk
213253 product_data = StripeProductData (name = f"Order #{ order_no } " )
214254
255+ # Add description if available
256+ if payment .description :
257+ product_data .description = payment .description
258+
215259 price_data = StripePriceData (
216260 currency = payment .currency .lower (),
217261 unit_amount = self .convert_amount (payment .currency , payment .total ),
@@ -246,7 +290,7 @@ def return_event_payload(self, request) -> Any:
246290 except ValueError as e :
247291 # Invalid payload
248292 raise e
249- except stripe .SignatureVerificationError as e :
293+ except stripe .error . SignatureVerificationError as e :
250294 # Invalid signature
251295 raise e
252296 else :
@@ -265,17 +309,19 @@ def get_token_from_request(self, payment, request) -> str:
265309 except Exception as e :
266310 raise PaymentError (
267311 code = 400 ,
268- message = "client_reference_id is not present in checkout.session event." ,
312+ message = (
313+ "client_reference_id is not present in checkout.session event."
314+ ),
269315 ) from e
270316
271317 # payment_intent events don't have client_reference_id
272- # These are follow-up webhooks - we already processed the payment in checkout.session
273- # Return None to signal this should be skipped by static_callback
318+ # These are follow-up webhooks - we already processed the payment
319+ # in checkout.session. Return None to signal skip by static_callback
274320 return None
275321
276322 def autocomplete_with_wallet (self , payment ):
277323 """
278- Complete payment using stored PaymentMethod (server-initiated recurring payment ).
324+ Complete payment using stored PaymentMethod (server-initiated).
279325
280326 This method charges a stored payment method without user interaction.
281327 Uses get_renew_data() to retrieve both payment_method_id and customer_id.
@@ -313,6 +359,10 @@ def autocomplete_with_wallet(self, payment):
313359 },
314360 }
315361
362+ # Add description if available (visible in Stripe Dashboard)
363+ if payment .description :
364+ intent_params ["description" ] = payment .description
365+
316366 intent = stripe .PaymentIntent .create (** intent_params )
317367
318368 payment .transaction_id = intent .id
@@ -400,11 +450,11 @@ def erase_wallet(self, wallet):
400450
401451 def _store_payment_method_from_session (self , payment , session_info ):
402452 """
403- Extract and store PaymentMethod and Customer from successful Checkout Session.
453+ Extract and store PaymentMethod and Customer from Checkout Session.
404454
405- Called after payment is confirmed to store payment method for future recurring charges.
406- Extracts customer_id from PaymentIntent (created by Stripe Checkout) and passes
407- it to implementer via set_renew_token().
455+ Called after payment is confirmed to store payment method for future
456+ recurring charges. Extracts customer_id from PaymentIntent (created by
457+ Stripe Checkout) and passes it to implementer via set_renew_token().
408458 """
409459 stripe .api_key = self .api_key
410460
@@ -467,8 +517,9 @@ def process_data(self, payment, request):
467517
468518 elif session_info ["payment_status" ] == "paid" :
469519 # Store PaymentMethod BEFORE changing status
470- # This is important because status change triggers signal that sets token_verified=True
471- # Use atomic transaction to prevent race conditions with concurrent webhooks
520+ # This is important because status change triggers signal that
521+ # sets token_verified=True. Use atomic transaction to prevent
522+ # race conditions with concurrent webhooks
472523 with transaction .atomic ():
473524 if self .store_payment_method and hasattr (
474525 payment , "set_renew_token"
0 commit comments