Repository: django-oscar/django-oscar

Issue:
build_submission doesn't include billing address
As far as I can tell, the submission dict should include the billing address. It's easily added here: https://github.com/tangentlabs/django-oscar/blob/master/oscar/apps/checkout/session.py#L252



Discussion:
In build_submission: the `get_billing_address(self, shipping_address)` is always returning `None` even when the shipping address is set as default billing address in the addressbook. Anything I am doing wrong ?

This will be addressed before we release 0.8.


Resolving patch:
diff --git a/oscar/apps/checkout/mixins.py b/oscar/apps/checkout/mixins.py
--- a/oscar/apps/checkout/mixins.py
+++ b/oscar/apps/checkout/mixins.py
@@ -97,7 +97,8 @@ def generate_order_number(self, basket):
 
     def handle_order_placement(self, order_number, user, basket,
                                shipping_address, shipping_method,
-                               shipping_charge, order_total, **kwargs):
+                               shipping_charge, billing_address, order_total,
+                               **kwargs):
         """
         Write out the order models and return the appropriate HTTP response
 
diff --git a/oscar/apps/checkout/session.py b/oscar/apps/checkout/session.py
--- a/oscar/apps/checkout/session.py
+++ b/oscar/apps/checkout/session.py
@@ -261,6 +261,7 @@ def build_submission(self, **kwargs):
         shipping_address = self.get_shipping_address(basket)
         shipping_method = self.get_shipping_method(
             basket, shipping_address)
+        billing_address = self.get_billing_address(shipping_address)
         if not shipping_method:
             total = shipping_charge = None
         else:
@@ -273,10 +274,19 @@ def build_submission(self, **kwargs):
             'shipping_address': shipping_address,
             'shipping_method': shipping_method,
             'shipping_charge': shipping_charge,
+            'billing_address': billing_address,
             'order_total': total,
             'order_kwargs': {},
             'payment_kwargs': {}}
 
+        # If there is a billing address, add it to the payment kwargs as calls
+        # to payment gateways generally require the billing address. Note, that
+        # it normally makes sense to pass the form instance that captures the
+        # billing address information. That way, if payment fails, you can
+        # render bound forms in the template to make re-submission easier.
+        if billing_address:
+            submission['payment_kwargs']['billing_address'] = billing_address
+
         # Allow overrides to be passed in
         submission.update(kwargs)
 
@@ -348,6 +358,13 @@ def get_shipping_method(self, basket, shipping_address=None, **kwargs):
     def get_billing_address(self, shipping_address):
         """
         Return an unsaved instance of the billing address (if one exists)
+
+        This method only returns a billing address if the session has been used
+        to store billing address information. It's also possible to capture
+        billing address information as part of the payment details forms, which
+        never get stored in the session. In that circumstance, the billing
+        address can be set directly in the build_submission dict (see Oscar's
+        demo site for an example of this approach).
         """
         if not self.checkout_session.is_billing_address_set():
             return None
@@ -359,10 +376,15 @@ def get_billing_address(self, shipping_address):
 
         addr_data = self.checkout_session.new_billing_address_fields()
         if addr_data:
-            # Load address data into a blank billing address model
+            # A new billing address has been entered - load address data into a
+            # blank billing address model.
             return BillingAddress(**addr_data)
+
         addr_id = self.checkout_session.billing_user_address_id()
         if addr_id:
+            # An address from the user's address book has been selected as the
+            # billing address - load it and convert it into a billing address
+            # instance.
             try:
                 user_address = UserAddress._default_manager.get(pk=addr_id)
             except UserAddress.DoesNotExist:
diff --git a/oscar/apps/checkout/views.py b/oscar/apps/checkout/views.py
--- a/oscar/apps/checkout/views.py
+++ b/oscar/apps/checkout/views.py
@@ -517,8 +517,8 @@ def get_default_billing_address(self):
             return None
 
     def submit(self, user, basket, shipping_address, shipping_method,  # noqa (too complex (10))
-               shipping_charge, order_total, payment_kwargs=None,
-               order_kwargs=None):
+               shipping_charge, billing_address, order_total,
+               payment_kwargs=None, order_kwargs=None):
         """
         Submit a basket for order placement.
 
@@ -534,7 +534,10 @@ def submit(self, user, basket, shipping_address, shipping_method,  # noqa (too c
            - If payment is unsuccessful, show an appropriate error message
 
         :basket: The basket to submit.
-        :payment_kwargs: Additional kwargs to pass to the handle_payment method
+        :payment_kwargs: Additional kwargs to pass to the handle_payment
+        method. It normally makes sense to pass form instances (rather than
+        model instances) so that the forms can be re-rendered correctly if
+        payment fails.
         :order_kwargs: Additional kwargs to pass to the place_order method
         """
         if payment_kwargs is None:
@@ -626,7 +629,7 @@ def submit(self, user, basket, shipping_address, shipping_method,  # noqa (too c
         try:
             return self.handle_order_placement(
                 order_number, user, basket, shipping_address, shipping_method,
-                shipping_charge, order_total, **order_kwargs)
+                shipping_charge, billing_address, order_total, **order_kwargs)
         except UnableToPlaceOrder as e:
             # It's possible that something will go wrong while trying to
             # actually place an order.  Not a good situation to be in as a


Test patch: