280 lines
8.1 KiB
Python
280 lines
8.1 KiB
Python
import base64
|
|
import hashlib
|
|
import hmac
|
|
import json
|
|
from decimal import Decimal
|
|
|
|
import pyDes
|
|
from django.contrib.auth import get_user_model
|
|
from django.db import transaction
|
|
from django.db.models import Sum
|
|
from django.utils import timezone
|
|
from django.utils.text import gettext_lazy as _
|
|
|
|
from shop.exceptions import RedsysPaymentException, RedsysValidationException
|
|
from shop.models import Cart, CartItem, Order, OrderLine, Payment, Product, ProductBatch, ProductPrice, ShippingMethod
|
|
from shop.settings import ERROR_CODES
|
|
from shop.signals import clear_cart
|
|
|
|
User = get_user_model()
|
|
|
|
|
|
def create_order_line_for_product(product: Product, quantity: Decimal, order: Order):
|
|
price = product.prices.last()
|
|
base_total = round(price.price * quantity, 2)
|
|
tax_value = price.tax.value / Decimal('100')
|
|
taxes = round(base_total * tax_value, 2)
|
|
|
|
return OrderLine.objects.create(
|
|
order=order,
|
|
product=product,
|
|
quantity=quantity,
|
|
price=price.price,
|
|
base_total=base_total,
|
|
tax_value=tax_value,
|
|
taxes=taxes,
|
|
total=base_total + taxes,
|
|
)
|
|
|
|
|
|
def create_order(
|
|
customer: User,
|
|
billing_address: str,
|
|
billing_city: str,
|
|
billing_state: str,
|
|
billing_country: str,
|
|
billing_zip: str,
|
|
shipping_address: str = '',
|
|
shipping_city: str = '',
|
|
shipping_state: str = '',
|
|
shipping_country: str = '',
|
|
shipping_zip: str = '',
|
|
) -> Order:
|
|
order = Order.objects.create(
|
|
user=customer,
|
|
billing_address=billing_address,
|
|
billing_city=billing_city,
|
|
billing_state=billing_state,
|
|
billing_country=billing_country,
|
|
billing_zip=billing_zip,
|
|
shipping_address=shipping_address,
|
|
shipping_city=shipping_city,
|
|
shipping_state=shipping_state,
|
|
shipping_country=shipping_country,
|
|
shipping_zip=shipping_zip,
|
|
)
|
|
|
|
return order
|
|
|
|
|
|
def delete_product_batch(batch: ProductBatch):
|
|
product = batch.product
|
|
product.stock = max(product.stock - batch.quantity, 0)
|
|
product.save()
|
|
batch.delete()
|
|
|
|
|
|
def create_order_from_cart(
|
|
cart: Cart,
|
|
shipping_method: ShippingMethod,
|
|
billing_address_full_name='',
|
|
billing_address_address='',
|
|
billing_address_town='',
|
|
billing_address_state='',
|
|
billing_address_country='',
|
|
billing_address_zip='',
|
|
shipping_address_full_name='',
|
|
shipping_address_address='',
|
|
shipping_address_town='',
|
|
shipping_address_state='',
|
|
shipping_address_country='',
|
|
shipping_address_zip='',
|
|
shipping_address_phone='',
|
|
email='',
|
|
):
|
|
order = Order.objects.create(
|
|
billing_address=f'{billing_address_full_name} {billing_address_address}',
|
|
billing_city=billing_address_town,
|
|
billing_state=billing_address_state,
|
|
billing_country=billing_address_country,
|
|
billing_zip=billing_address_zip,
|
|
contact_phone=shipping_address_phone,
|
|
shipping_address=f'{shipping_address_address} {shipping_address_full_name}',
|
|
shipping_city=shipping_address_town,
|
|
shipping_state=shipping_address_state,
|
|
shipping_country=shipping_address_country,
|
|
shipping_zip=shipping_address_zip,
|
|
shipping_method=shipping_method,
|
|
user=cart.user,
|
|
email=email,
|
|
from_cart=cart,
|
|
)
|
|
|
|
for item in cart.items.all():
|
|
product = item.product
|
|
price: ProductPrice = product.price
|
|
base_total = price.price * item.quantity
|
|
tax_value = price.tax.value
|
|
taxes = base_total * tax_value / Decimal(100)
|
|
total = base_total + taxes
|
|
|
|
OrderLine.objects.create(
|
|
order=order,
|
|
product=product,
|
|
quantity=item.quantity,
|
|
price=price.price,
|
|
base_total=base_total,
|
|
tax_value=tax_value,
|
|
taxes=taxes,
|
|
total=total,
|
|
)
|
|
|
|
order.calculate_total_from_lines()
|
|
add_shipping_order_line(order, shipping_method)
|
|
|
|
return order
|
|
|
|
|
|
def add_shipping_order_line(order, shipping_method):
|
|
shipping_price: ProductPrice = shipping_method.shipping_product.price
|
|
shipping_base_total = shipping_price.price
|
|
shipping_tax_value = shipping_price.tax.value
|
|
shipping_taxes = shipping_base_total * (shipping_tax_value / Decimal(100))
|
|
shipping_total = shipping_base_total + shipping_taxes
|
|
|
|
OrderLine.objects.create(
|
|
order=order,
|
|
product=shipping_method.shipping_product,
|
|
quantity=1,
|
|
price=shipping_price.price,
|
|
base_total=shipping_base_total,
|
|
tax_value=shipping_tax_value,
|
|
taxes=shipping_taxes,
|
|
total=shipping_total,
|
|
)
|
|
order.calculate_total_from_lines()
|
|
|
|
|
|
def compute_signature(salt, payload, key):
|
|
"""
|
|
:param salt: order number (Ds_Order or Ds_Merchant_Order)
|
|
:param payload: Ds_MerchantParameters
|
|
:param key: shared secret (aka key) from the Redsys Administration Module
|
|
:return:
|
|
"""
|
|
b64_key = base64.b64decode(key)
|
|
|
|
des3 = pyDes.triple_des(b64_key, mode=pyDes.CBC, IV='\0' * 8, pad='\0', padmode=pyDes.PAD_NORMAL)
|
|
pepper = des3.encrypt(str(salt))
|
|
|
|
payload_hash = hmac.new(pepper, payload.encode(), hashlib.sha256).digest()
|
|
return base64.b64encode(payload_hash)
|
|
|
|
|
|
def validate_payment_for_order(request, order: Order) -> Decimal:
|
|
"""
|
|
example_response_data = {
|
|
'Ds_MerchantCode': '999008881',
|
|
'Ds_Terminal': '001',
|
|
'Ds_Order': 'a082bd1cda0b488786f23a62e89107c0',
|
|
'Ds_Amount': '13532',
|
|
'Ds_Currency': '978',
|
|
'Ds_Date': '20/03/2024',
|
|
'Ds_Hour': '12:21',
|
|
'Ds_SecurePayment': '1',
|
|
'Ds_Card_Number': '454881******1156',
|
|
'Ds_Card_Country': '724',
|
|
'Ds_Response': '0000',
|
|
'Ds_MerchantData': '',
|
|
'Ds_TransactionType': '0',
|
|
'Ds_ConsumerLanguage': '1',
|
|
'Ds_AuthorisationCode': '182670',
|
|
'Ds_Card_Brand': '1',
|
|
'Ds_ProcessedPayMethod': '80',
|
|
'Ds_ECI': '05',
|
|
'Ds_Response_Description': 'OPERACION AUTORIZADA'
|
|
}
|
|
|
|
Raises AssertionError
|
|
"""
|
|
data = request.POST
|
|
|
|
merchant_parameters = data.get('Ds_MerchantParameters')
|
|
|
|
if not merchant_parameters:
|
|
raise RedsysValidationException(_('No se ha recibido ningún valor para Ds_MerchantParameters'))
|
|
|
|
merchant_params = decode_b64_string(merchant_parameters)
|
|
result = json.loads(merchant_params)
|
|
|
|
order_code = result.get('Ds_Order')
|
|
assert order_code == order.code
|
|
|
|
status_code = result.get('Ds_Response')
|
|
|
|
if int(status_code) > 100:
|
|
reason = ERROR_CODES.get(status_code, _('Error no tipificado'))
|
|
|
|
raise RedsysPaymentException(_(f'No se ha realizado el pago. Motivo: {reason}'))
|
|
|
|
amount = Decimal(result.get('Ds_Amount')) / 100
|
|
|
|
return amount
|
|
|
|
|
|
def decode_b64_string(value: str):
|
|
return base64.b64decode(value).decode()
|
|
|
|
|
|
def decode_b64_dict(value: str):
|
|
return json.loads(decode_b64_string(value))
|
|
|
|
|
|
def validate_expiry_date(expiry_date: str):
|
|
if len(expiry_date) != 4:
|
|
return False
|
|
|
|
month, year = int(expiry_date[2:4]), int(expiry_date[:2])
|
|
|
|
if month < 1 or month > 12:
|
|
return False
|
|
|
|
current_year = int(str(timezone.now().year)[2:])
|
|
current_month = timezone.now().month
|
|
|
|
if year < current_year:
|
|
return False
|
|
|
|
if year == current_year and month > current_month:
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
def update_order_payment_status(order: Order):
|
|
if order.amount_paid >= order.total:
|
|
order.status = Order.Statuses.STATUS_PAID
|
|
elif order.status == Order.Statuses.STATUS_PAID and order.amount_paid == Decimal('0.00'):
|
|
order.status = Order.Statuses.STATUS_RETURNED
|
|
else:
|
|
order.status = Order.Statuses.STATUS_PENDING
|
|
|
|
order.save()
|
|
|
|
|
|
def delete_cart_items_from_order(order):
|
|
if order.status == Order.Statuses.STATUS_PAID and order.from_cart:
|
|
CartItem.objects.filter(cart=order.from_cart).delete()
|
|
|
|
|
|
def add_payment_to_order(order: Order, amount):
|
|
with transaction.atomic():
|
|
payment = Payment.objects.create(
|
|
amount=amount, order=order, user=order.user, method=Payment.MethodChoices.REDSYS
|
|
)
|
|
order.amount_paid += payment.amount
|
|
|
|
update_order_payment_status(order)
|
|
|
|
delete_cart_items_from_order(order)
|