Merge branch 'release/0.5.2'
This commit is contained in:
commit
7b3b4bacfa
@ -36,6 +36,15 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="object__panel">
|
||||
<div class="object__item object__item--header">
|
||||
<h4>Shipping</h4>
|
||||
</div>
|
||||
<div class="panel__item">
|
||||
<a href="{% url 'dashboard:order-fulfill' order.pk %}" class="action-button order__fulfill">Ship order →</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="object__panel">
|
||||
<div class="object__item object__item--header">
|
||||
<h4>Customer</h4>
|
||||
@ -70,10 +79,10 @@
|
||||
|
||||
<section class="object__panel">
|
||||
<div class="object__item object__item--header">
|
||||
<h4>PayPal</h4>
|
||||
<h4>Transaction</h4>
|
||||
</div>
|
||||
<div class="panel__item">
|
||||
<p>Transaction: <strong>{{order.transaction.paypal_id}}</strong><br>
|
||||
<p>PayPal transaction ID: <strong>{{order.transaction.paypal_id}}</strong><br>
|
||||
Status: <strong>{{order.transaction.get_status_display}}</strong>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@ -24,6 +24,7 @@ INSTALLED_APPS = [
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
'django.contrib.sites',
|
||||
|
||||
# 3rd Party
|
||||
'django_filters',
|
||||
|
||||
@ -2,6 +2,7 @@ import logging
|
||||
from decimal import Decimal
|
||||
from django.conf import settings
|
||||
from core.models import Product, OrderLine
|
||||
from .payments import CreateOrder
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@ -65,59 +66,39 @@ class Cart:
|
||||
del self.session[settings.CART_SESSION_ID]
|
||||
self.session.modified = True
|
||||
|
||||
def get_bulk_list_and_body_data(self, order, shipping_address=None):
|
||||
bulk_list = []
|
||||
|
||||
body_data = {
|
||||
'intent': 'CAPTURE',
|
||||
'purchase_units': [{
|
||||
'amount': {
|
||||
'currency_code': 'USD',
|
||||
'value': f'{self.get_total_price()}',
|
||||
'breakdown': {
|
||||
# Required when including the `items` array
|
||||
'item_total': {
|
||||
'currency_code': 'USD',
|
||||
'value': f'{self.get_total_price()}'
|
||||
}
|
||||
}
|
||||
},
|
||||
'items': []
|
||||
}]
|
||||
def build_order_params(self):
|
||||
return \
|
||||
{
|
||||
'items': self,
|
||||
'total_price': f'{self.get_total_price()}',
|
||||
'item_total': f'{self.get_total_price()}',
|
||||
'shipping_price': '0',
|
||||
'tax_total': '0',
|
||||
'shipping_method': 'US POSTAL SERVICE',
|
||||
'shipping_address': self.build_shipping_address(self.session.get('shipping_address')),
|
||||
}
|
||||
|
||||
if shipping_address:
|
||||
body_data['purchase_units'][0]['shipping'] = self.process_shipping_address(shipping_address)
|
||||
def create_order(self):
|
||||
params = self.build_order_params()
|
||||
logger.info(f'\nParams: {params}\n')
|
||||
response = CreateOrder().create_order(params, debug=True)
|
||||
return response
|
||||
|
||||
for item in self:
|
||||
body_data['purchase_units'][0]['items'].append({
|
||||
# Shows within upper-right dropdown during payment approval
|
||||
'name': f'{item["product"]}',
|
||||
# Item details will also be in the completed paypal.com transaction view
|
||||
'description': 'Coffee',
|
||||
'unit_amount': {
|
||||
'currency_code': 'USD',
|
||||
'value': f'{item["price"]}'
|
||||
},
|
||||
'quantity': f'{item["quantity"]}'
|
||||
})
|
||||
|
||||
bulk_list.append(
|
||||
OrderLine(
|
||||
def build_bulk_list(self, order):
|
||||
bulk_list = [OrderLine(
|
||||
order=order,
|
||||
product=item['product'],
|
||||
customer_note=f'{item["roast"]} {item["other"]}',
|
||||
unit_price=item['price'],
|
||||
quantity=item['quantity'],
|
||||
tax_rate=2,
|
||||
)
|
||||
)
|
||||
) for item in self]
|
||||
|
||||
return bulk_list, body_data
|
||||
return bulk_list
|
||||
|
||||
def process_shipping_address(self, address):
|
||||
shipping = {
|
||||
'address': {
|
||||
def build_shipping_address(self, address):
|
||||
return \
|
||||
{
|
||||
'address_line_1': f'{address["street_address_1"]}',
|
||||
'address_line_2': f'{address["street_address_2"]}',
|
||||
'admin_area_2': f'{address["city"]}',
|
||||
@ -125,8 +106,6 @@ class Cart:
|
||||
'postal_code': f'{address["postal_code"]}',
|
||||
'country_code': 'US'
|
||||
}
|
||||
}
|
||||
return shipping
|
||||
|
||||
# @property
|
||||
# def coupon(self):
|
||||
|
||||
166
src/storefront/payments.py
Normal file
166
src/storefront/payments.py
Normal file
@ -0,0 +1,166 @@
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import logging
|
||||
|
||||
from paypalcheckoutsdk.core import PayPalHttpClient, SandboxEnvironment
|
||||
from paypalcheckoutsdk.orders import OrdersCreateRequest, OrdersCaptureRequest
|
||||
from paypalhttp.serializers.json_serializer import Json
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.sites.models import Site
|
||||
from django.urls import reverse_lazy
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class PayPalClient:
|
||||
def __init__(self):
|
||||
self.client_id = settings.PAYPAL_CLIENT_ID
|
||||
self.client_secret = settings.PAYPAL_SECRET_ID
|
||||
self.site_domain = Site.objects.get_current().domain
|
||||
self.site_name = Site.objects.get_current().name
|
||||
|
||||
"""Setting up and Returns PayPal SDK environment with PayPal Access credentials.
|
||||
For demo purpose, we are using SandboxEnvironment. In production this will be
|
||||
LiveEnvironment."""
|
||||
self.environment = SandboxEnvironment(client_id=self.client_id, client_secret=self.client_secret)
|
||||
|
||||
""" Returns PayPal HTTP client instance with environment which has access
|
||||
credentials context. This can be used invoke PayPal API's provided the
|
||||
credentials have the access to do so. """
|
||||
self.client = PayPalHttpClient(self.environment)
|
||||
|
||||
def object_to_json(self, json_data):
|
||||
"""
|
||||
Function to print all json data in an organized readable manner
|
||||
"""
|
||||
result = {}
|
||||
if sys.version_info[0] < 3:
|
||||
itr = json_data.__dict__.iteritems()
|
||||
else:
|
||||
itr = json_data.__dict__.items()
|
||||
for key,value in itr:
|
||||
# Skip internal attributes.
|
||||
if key.startswith("__") or key.startswith("_"):
|
||||
continue
|
||||
result[key] = self.array_to_json_array(value) if isinstance(value, list) else\
|
||||
self.object_to_json(value) if not self.is_primittive(value) else\
|
||||
value
|
||||
return result
|
||||
|
||||
def array_to_json_array(self, json_array):
|
||||
result =[]
|
||||
if isinstance(json_array, list):
|
||||
for item in json_array:
|
||||
result.append(self.object_to_json(item) if not self.is_primittive(item) \
|
||||
else self.array_to_json_array(item) if isinstance(item, list) else item)
|
||||
return result
|
||||
|
||||
def is_primittive(self, data):
|
||||
return isinstance(data, str) or isinstance(data, unicode) or isinstance(data, int)
|
||||
|
||||
|
||||
class CreateOrder(PayPalClient):
|
||||
|
||||
"""Setting up the JSON request body for creating the Order. The Intent in the
|
||||
request body should be set as "CAPTURE" for capture intent flow."""
|
||||
|
||||
def build_request_body(self, params):
|
||||
"""Method to create body with CAPTURE intent"""
|
||||
processed_items = [{
|
||||
# Shows within upper-right dropdown during payment approval
|
||||
'name': f'{item["product"]}',
|
||||
# Item details will also be in the completed paypal.com transaction view
|
||||
'description': 'Coffee',
|
||||
'unit_amount': {
|
||||
'currency_code': 'USD',
|
||||
'value': f'{item["price"]}'
|
||||
},
|
||||
'quantity': f'{item["quantity"]}'
|
||||
} for item in params['items']]
|
||||
|
||||
request_body = {
|
||||
"intent": "CAPTURE",
|
||||
"application_context": {
|
||||
"return_url": f"https://{self.site_domain}{reverse_lazy('storefront:payment-done')}",
|
||||
"cancel_url": f"https://{self.site_domain}{reverse_lazy('storefront:payment-canceled')}",
|
||||
"brand_name": f"{self.site_name}",
|
||||
# "landing_page": "BILLING",
|
||||
# "shipping_preference": "SET_PROVIDED_ADDRESS",
|
||||
# "user_action": "CONTINUE"
|
||||
},
|
||||
"purchase_units": [
|
||||
{
|
||||
# "reference_id": "PUHF",
|
||||
"description": "Coffee",
|
||||
|
||||
# "custom_id": "CUST-HighFashions",
|
||||
# "soft_descriptor": "HighFashions",
|
||||
"amount": {
|
||||
"currency_code": "USD",
|
||||
"value": params['total_price'],
|
||||
"breakdown": {
|
||||
"item_total": {
|
||||
"currency_code": "USD",
|
||||
"value": params['item_total']
|
||||
},
|
||||
"shipping": {
|
||||
"currency_code": "USD",
|
||||
"value": params['shipping_price']
|
||||
},
|
||||
"tax_total": {
|
||||
"currency_code": "USD",
|
||||
"value": params['tax_total']
|
||||
},
|
||||
# "shipping_discount": {
|
||||
# "currency_code": "USD",
|
||||
# "value": "10"
|
||||
# }
|
||||
}
|
||||
},
|
||||
"items": processed_items,
|
||||
"shipping": {
|
||||
"method": params['shipping_method'],
|
||||
"address": params['shipping_address']
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
logger.info(f'\nRequest body: {request_body}\n')
|
||||
|
||||
return request_body
|
||||
|
||||
""" This is the sample function which can be sued to create an order. It uses the
|
||||
JSON body returned by buildRequestBody() to create an new Order."""
|
||||
|
||||
def create_order(self, params, debug=False):
|
||||
request = OrdersCreateRequest()
|
||||
request.headers['prefer'] = 'return=representation'
|
||||
request.request_body(self.build_request_body(params))
|
||||
response = self.client.execute(request)
|
||||
if debug:
|
||||
logger.info(f'\nStatus Code: {response.status_code}', )
|
||||
logger.info(f'\nStatus: {response.result.status}', )
|
||||
logger.info(f'\nOrder ID: {response.result.id}', )
|
||||
logger.info(f'\nIntent: {response.result.intent}', )
|
||||
logger.info(f"\njson_data: {response.result}")
|
||||
|
||||
return response
|
||||
|
||||
class CaptureOrder(PayPalClient):
|
||||
|
||||
"""this is the sample function performing payment capture on the order. Approved Order id should be passed as an argument to this function"""
|
||||
|
||||
def capture_order(self, order_id, debug=False):
|
||||
"""Method to capture order using order_id"""
|
||||
request = OrdersCaptureRequest(order_id)
|
||||
response = self.client.execute(request)
|
||||
|
||||
data = response.result.__dict__['_dict']
|
||||
data['redirect_urls'] = {
|
||||
'return_url': f"https://{self.site_domain}{reverse_lazy('storefront:payment-done')}",
|
||||
'cancel_url': f"https://{self.site_domain}{reverse_lazy('storefront:payment-canceled')}"
|
||||
}
|
||||
|
||||
return data
|
||||
@ -22,6 +22,7 @@ from core.forms import ShippingMethodForm
|
||||
|
||||
from .forms import AddToCartForm, OrderCreateForm, AddressForm
|
||||
from .cart import Cart
|
||||
from .payments import CaptureOrder
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@ -91,28 +92,6 @@ class ProductDetailView(FormMixin, DetailView):
|
||||
template_name = 'storefront/product_detail.html'
|
||||
form_class = AddToCartForm
|
||||
|
||||
def paypal_order_transaction_capture(request, transaction_id):
|
||||
if request.method =="POST":
|
||||
capture_order = OrdersCaptureRequest(transaction_id)
|
||||
environment = SandboxEnvironment(client_id=settings.PAYPAL_CLIENT_ID, client_secret=settings.PAYPAL_SECRET_ID)
|
||||
client = PayPalHttpClient(environment)
|
||||
|
||||
response = client.execute(capture_order)
|
||||
data = response.result.__dict__['_dict']
|
||||
data['redirect_urls'] = {
|
||||
'return_url': request.build_absolute_uri(reverse_lazy('storefront:payment-done')),
|
||||
'cancel_url': request.build_absolute_uri(reverse_lazy('storefront:payment-canceled'))
|
||||
}
|
||||
transaction = Transaction.objects.get(order__pk=request.session.get('order_id'))
|
||||
transaction.paypal_id = data['purchase_units'][0]['payments']['captures'][0]['id']
|
||||
transaction.status = data['status']
|
||||
transaction.save()
|
||||
logger.debug(f'\nPayPal Response data: {data}\n')
|
||||
|
||||
return JsonResponse(data)
|
||||
else:
|
||||
return JsonResponse({'details': 'invalid request'})
|
||||
|
||||
|
||||
class CheckoutAddressView(FormView):
|
||||
template_name = 'storefront/checkout_address.html'
|
||||
@ -177,32 +156,34 @@ class OrderCreateView(CreateView):
|
||||
return context
|
||||
|
||||
def form_valid(self, form):
|
||||
cart = Cart(self.request)
|
||||
shipping_address = self.request.session.get('shipping_address')
|
||||
form.instance.customer, form.instance.shipping_address = get_or_create_customer(self.request, form, shipping_address)
|
||||
self.object = form.save()
|
||||
|
||||
# Cart data setup
|
||||
cart = Cart(self.request)
|
||||
bulk_list, body_data = cart.get_bulk_list_and_body_data(self.object, shipping_address)
|
||||
logger.debug(f'\nBody data: {body_data}\n')
|
||||
|
||||
# Bulk create OrderLine objects from cart items
|
||||
bulk_list = cart.build_bulk_list(self.object)
|
||||
objs = OrderLine.objects.bulk_create(bulk_list)
|
||||
|
||||
# PayPal setup
|
||||
environment = SandboxEnvironment(client_id=settings.PAYPAL_CLIENT_ID, client_secret=settings.PAYPAL_SECRET_ID)
|
||||
client = PayPalHttpClient(environment)
|
||||
create_order = OrdersCreateRequest()
|
||||
create_order.request_body(body_data)
|
||||
|
||||
response = client.execute(create_order)
|
||||
response = cart.create_order()
|
||||
data = response.result.__dict__['_dict']
|
||||
|
||||
cart.clear()
|
||||
self.request.session['order_id'] = self.object.id
|
||||
self.request.session['order_id'] = self.object.pk
|
||||
|
||||
return JsonResponse(data)
|
||||
|
||||
def paypal_order_transaction_capture(request, transaction_id):
|
||||
if request.method =="POST":
|
||||
data = CaptureOrder().capture_order(transaction_id)
|
||||
|
||||
transaction = Transaction.objects.get(order__pk=request.session.get('order_id'))
|
||||
transaction.paypal_id = data['purchase_units'][0]['payments']['captures'][0]['id']
|
||||
transaction.status = data['status']
|
||||
transaction.save()
|
||||
logger.debug(f'\nPayPal Response data: {data}\n')
|
||||
|
||||
return JsonResponse(data)
|
||||
else:
|
||||
return JsonResponse({'details': 'invalid request'})
|
||||
|
||||
|
||||
class PaymentDoneView(TemplateView):
|
||||
template_name = 'storefront/payment_done.html'
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user