mirror of
https://github.com/ae-utbm/sith.git
synced 2025-07-10 11:59:23 +00:00
integration of 3D secure v2 for eboutic bank payment
This commit is contained in:
@ -138,7 +138,7 @@ class BasketForm:
|
||||
continue
|
||||
if type(item["quantity"]) is not int or item["quantity"] < 0:
|
||||
self.error_messages.add(
|
||||
_("You cannot buy %(nbr)d %(name)%s.")
|
||||
_("You cannot buy %(nbr)d %(name)s.")
|
||||
% {"nbr": item["quantity"], "name": item["name"]}
|
||||
)
|
||||
continue
|
||||
@ -166,7 +166,6 @@ class BasketForm:
|
||||
return True
|
||||
|
||||
def get_error_messages(self) -> typing.List[str]:
|
||||
# return [msg for msg in self.error_messages]
|
||||
return list(self.error_messages)
|
||||
|
||||
def get_cleaned_cookie(self) -> str:
|
||||
|
@ -21,9 +21,12 @@
|
||||
# Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
#
|
||||
#
|
||||
import hmac
|
||||
import typing
|
||||
from datetime import datetime
|
||||
from typing import List
|
||||
|
||||
from dict2xml import dict2xml
|
||||
from django.conf import settings
|
||||
from django.db import models, DataError
|
||||
from django.db.models import Sum, F
|
||||
@ -32,7 +35,7 @@ from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from accounting.models import CurrencyField
|
||||
from core.models import Group, User
|
||||
from counter.models import Counter, Product, Selling, Refilling
|
||||
from counter.models import Counter, Product, Selling, Refilling, BillingInfo, Customer
|
||||
|
||||
|
||||
def get_eboutic_products(user: User) -> List[Product]:
|
||||
@ -104,7 +107,7 @@ class Basket(models.Model):
|
||||
"""
|
||||
Remove all items from this basket without deleting the basket
|
||||
"""
|
||||
BasketItem.objects.filter(basket=self).delete()
|
||||
self.items.all().delete()
|
||||
|
||||
@cached_property
|
||||
def contains_refilling_item(self) -> bool:
|
||||
@ -122,7 +125,7 @@ class Basket(models.Model):
|
||||
def from_session(cls, session) -> typing.Union["Basket", None]:
|
||||
"""
|
||||
Given an HttpRequest django object, return the basket used in the current session
|
||||
if it exists else create a new one and return it
|
||||
if it exists else None
|
||||
"""
|
||||
if "basket_id" in session:
|
||||
try:
|
||||
@ -131,6 +134,93 @@ class Basket(models.Model):
|
||||
return None
|
||||
return None
|
||||
|
||||
def generate_sales(self, counter, seller: User, payment_method: str):
|
||||
"""
|
||||
Generate a list of sold items corresponding to the items
|
||||
of this basket WITHOUT saving them NOR deleting the basket
|
||||
|
||||
Example:
|
||||
::
|
||||
|
||||
counter = Counter.objects.get(name="Eboutic")
|
||||
sales = basket.generate_sales(counter, "SITH_ACCOUNT")
|
||||
# here the basket is in the same state as before the method call
|
||||
|
||||
with transaction.atomic():
|
||||
for sale in sales:
|
||||
sale.save()
|
||||
basket.delete()
|
||||
# all the basket items are deleted by the on_delete=CASCADE relation
|
||||
# thus only the sales remain
|
||||
"""
|
||||
# I must proceed with two distinct requests instead of
|
||||
# only one with a join because the AbstractBaseItem model has been
|
||||
# poorly designed. If you refactor the model, please refactor this too.
|
||||
items = self.items.order_by("product_id")
|
||||
ids = [item.product_id for item in items]
|
||||
products = Product.objects.filter(id__in=ids).order_by("id")
|
||||
# items and products are sorted in the same order
|
||||
sales = []
|
||||
for item, product in zip(items, products):
|
||||
sales.append(
|
||||
Selling(
|
||||
label=product.name,
|
||||
counter=counter,
|
||||
club=product.club,
|
||||
product=product,
|
||||
seller=seller,
|
||||
customer=self.user.customer,
|
||||
unit_price=item.product_unit_price,
|
||||
quantity=item.quantity,
|
||||
payment_method=payment_method,
|
||||
)
|
||||
)
|
||||
return sales
|
||||
|
||||
def get_e_transaction_data(self):
|
||||
user = self.user
|
||||
if not hasattr(user, "customer"):
|
||||
raise Customer.DoesNotExist
|
||||
customer = user.customer
|
||||
if not hasattr(user.customer, "billing_infos"):
|
||||
raise BillingInfo.DoesNotExist
|
||||
data = [
|
||||
("PBX_SITE", settings.SITH_EBOUTIC_PBX_SITE),
|
||||
("PBX_RANG", settings.SITH_EBOUTIC_PBX_RANG),
|
||||
("PBX_IDENTIFIANT", settings.SITH_EBOUTIC_PBX_IDENTIFIANT),
|
||||
("PBX_TOTAL", str(int(self.get_total() * 100))),
|
||||
("PBX_DEVISE", "978"), # This is Euro
|
||||
("PBX_CMD", str(self.id)),
|
||||
("PBX_PORTEUR", user.email),
|
||||
("PBX_RETOUR", "Amount:M;BasketID:R;Auto:A;Error:E;Sig:K"),
|
||||
("PBX_HASH", "SHA512"),
|
||||
("PBX_TYPEPAIEMENT", "CARTE"),
|
||||
("PBX_TYPECARTE", "CB"),
|
||||
("PBX_TIME", datetime.now().replace(microsecond=0).isoformat("T")),
|
||||
("PBX_BILLING", customer.billing_infos.to_3dsv2_xml()),
|
||||
(
|
||||
"PBX_SHOPPINGCART",
|
||||
dict2xml({"shoppingcart": {"total": {min(self.items.count(), 99)}}}),
|
||||
),
|
||||
]
|
||||
data.append(
|
||||
(
|
||||
"PBX_HMAC",
|
||||
(
|
||||
hmac.new(
|
||||
settings.SITH_EBOUTIC_HMAC_KEY,
|
||||
bytes("&".join("=".join(d) for d in data), "utf-8"),
|
||||
"sha512",
|
||||
)
|
||||
.hexdigest()
|
||||
.upper()
|
||||
),
|
||||
)
|
||||
)
|
||||
return data
|
||||
|
||||
# def validate(self, exclude=None):
|
||||
|
||||
def __str__(self):
|
||||
return "%s's basket (%d items)" % (self.user, self.items.all().count())
|
||||
|
||||
@ -156,18 +246,9 @@ class Invoice(models.Model):
|
||||
)["total"]
|
||||
return float(total) if total is not None else 0
|
||||
|
||||
def validate(self, *args, **kwargs):
|
||||
def validate(self):
|
||||
if self.validated:
|
||||
raise DataError(_("Invoice already validated"))
|
||||
from counter.models import Customer
|
||||
|
||||
if not Customer.objects.filter(user=self.user).exists():
|
||||
number = Customer.objects.count() + 1
|
||||
Customer(
|
||||
user=self.user,
|
||||
account_id=Customer.generate_account_id(number),
|
||||
amount=0,
|
||||
).save()
|
||||
eboutic = Counter.objects.filter(type="EBOUTIC").first()
|
||||
for i in self.items.all():
|
||||
if i.type_id == settings.SITH_COUNTER_PRODUCTTYPE_REFILLING:
|
||||
@ -227,6 +308,22 @@ class BasketItem(AbstractBaseItem):
|
||||
Basket, related_name="items", verbose_name=_("basket"), on_delete=models.CASCADE
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_product(cls, product: Product, quantity: int):
|
||||
"""
|
||||
Create a BasketItem with the same characteristics as the
|
||||
product passed in parameters, with the specified quantity
|
||||
WARNING : the basket field is not filled, so you must set
|
||||
it yourself before saving the model
|
||||
"""
|
||||
return cls(
|
||||
product_id=product.id,
|
||||
product_name=product.name,
|
||||
type_id=product.product_type.id,
|
||||
quantity=quantity,
|
||||
product_unit_price=product.selling_price,
|
||||
)
|
||||
|
||||
|
||||
class InvoiceItem(AbstractBaseItem):
|
||||
invoice = models.ForeignKey(
|
||||
|
177
eboutic/static/eboutic/css/eboutic.css
Normal file
177
eboutic/static/eboutic/css/eboutic.css
Normal file
@ -0,0 +1,177 @@
|
||||
#eboutic {
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
align-items: flex-start;
|
||||
column-gap: 20px;
|
||||
margin: 0 20px 20px;
|
||||
}
|
||||
|
||||
#eboutic-title {
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
#eboutic h3 {
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
#basket {
|
||||
min-width: 300px;
|
||||
border-radius: 8px;
|
||||
box-shadow: rgb(60 64 67 / 30%) 0 1px 3px 0, rgb(60 64 67 / 15%) 0 4px 8px 3px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
#basket h3 {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 765px) {
|
||||
#eboutic {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin: 10px;
|
||||
row-gap: 20px;
|
||||
}
|
||||
#eboutic-title {
|
||||
margin-bottom: 20px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
#basket {
|
||||
}
|
||||
}
|
||||
|
||||
#eboutic .item-list {
|
||||
margin-left: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
#eboutic .item-list li {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 10px
|
||||
}
|
||||
|
||||
#eboutic .item-name {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
#eboutic .item-price, #eboutic .item-quantity {
|
||||
width: 65px;
|
||||
}
|
||||
|
||||
#eboutic .item-quantity {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#eboutic .fa-plus, #eboutic .fa-minus {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#eboutic .item-price {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/* CSS du catalogue */
|
||||
|
||||
#eboutic #catalog {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
flex-direction: column;
|
||||
row-gap: 30px;
|
||||
}
|
||||
|
||||
#eboutic .category-header {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
#eboutic .product-group {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
column-gap: 15px;
|
||||
row-gap: 15px;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 765px) {
|
||||
#eboutic #catalog {
|
||||
row-gap: 15px;
|
||||
}
|
||||
|
||||
#eboutic section {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#eboutic .product-group {
|
||||
justify-content: space-around;
|
||||
}
|
||||
}
|
||||
|
||||
#eboutic .product-button {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
min-width: 120px;
|
||||
max-width: 150px;
|
||||
padding: 10px;
|
||||
overflow: hidden;
|
||||
box-shadow: rgb(60 64 67 / 30%) 0 1px 3px 0, rgb(60 64 67 / 15%) 0 4px 8px 3px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
row-gap: 5px;
|
||||
}
|
||||
|
||||
#eboutic .product-button:active {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
#eboutic .product-button img, #eboutic .product-button .fa {
|
||||
margin: 0;
|
||||
border-radius: 4px;
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
}
|
||||
|
||||
#eboutic .product-button p {
|
||||
font-size: 13px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#eboutic .catalog-buttons {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
column-gap: 30px;
|
||||
margin: 30px 0 0;
|
||||
}
|
||||
|
||||
#eboutic input {
|
||||
all: unset;
|
||||
}
|
||||
|
||||
#eboutic .catalog-buttons button {
|
||||
font-size: 15px!important;
|
||||
font-weight: normal;
|
||||
color: white;
|
||||
min-width: 60px;
|
||||
padding: 5px 10px;
|
||||
}
|
||||
#eboutic .catalog-buttons .validate {
|
||||
background-color: #354a5f;
|
||||
}
|
||||
#eboutic .catalog-buttons .clear {
|
||||
background-color: gray;
|
||||
}
|
||||
#eboutic .catalog-buttons button i {
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
#eboutic .catalog-buttons button.validate:hover {
|
||||
background-color: #2c3646;
|
||||
}
|
||||
|
||||
#eboutic .catalog-buttons button.clear:hover {
|
||||
background-color:hsl(210,5%,30%);
|
||||
}
|
||||
|
||||
#eboutic .catalog-buttons form {
|
||||
margin: 0;
|
||||
}
|
104
eboutic/static/eboutic/js/eboutic.js
Normal file
104
eboutic/static/eboutic/js/eboutic.js
Normal file
@ -0,0 +1,104 @@
|
||||
function getCookie(name) {
|
||||
let cookieValue = null;
|
||||
if (document.cookie && document.cookie !== '') {
|
||||
const cookies = document.cookie.split(';');
|
||||
for (let i = 0; i < cookies.length; i++) {
|
||||
const cookie = cookies[i].trim();
|
||||
// Does this cookie string begin with the name we want?
|
||||
if (cookie.substring(0, name.length + 1) === (name + '=')) {
|
||||
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return cookieValue;
|
||||
}
|
||||
|
||||
function get_starting_items() {
|
||||
const cookie = getCookie("basket_items")
|
||||
try {
|
||||
// django cookie backend does an utter mess on non-trivial data types
|
||||
// so we must perform a conversion of our own
|
||||
const biscuit = JSON.parse(JSON.parse(cookie.replace(/\\054/g, ',')));
|
||||
if (Array.isArray(biscuit)) {
|
||||
return biscuit;
|
||||
}
|
||||
return [];
|
||||
} catch (e) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('alpine:init', () => {
|
||||
Alpine.data('basket', () => ({
|
||||
items: get_starting_items(),
|
||||
|
||||
get_total() {
|
||||
let total = 0;
|
||||
for (const item of this.items) {
|
||||
total += item["quantity"] * item["unit_price"];
|
||||
}
|
||||
return total;
|
||||
},
|
||||
|
||||
add(item) {
|
||||
item.quantity++;
|
||||
this.edit_cookies()
|
||||
},
|
||||
|
||||
remove(item_id) {
|
||||
const index = this.items.findIndex(e => e.id === item_id);
|
||||
if (index < 0) return;
|
||||
this.items[index].quantity -= 1;
|
||||
if (this.items[index].quantity === 0) {
|
||||
this.items = this.items.filter((e) => e.id !== this.items[index].id);
|
||||
}
|
||||
this.edit_cookies();
|
||||
},
|
||||
|
||||
clear_basket() {
|
||||
this.items = []
|
||||
this.edit_cookies();
|
||||
},
|
||||
|
||||
edit_cookies() {
|
||||
// a cookie survives an hour
|
||||
document.cookie = "basket_items=" + JSON.stringify(this.items) + ";Max-Age=3600";
|
||||
},
|
||||
|
||||
/**
|
||||
* Create an item in the basket if it was not already in
|
||||
* @param id : int the id of the product to add
|
||||
* @param name : String the name of the product
|
||||
* @param price : number the unit price of the product
|
||||
*/
|
||||
create_item(id, name, price) {
|
||||
let new_item = {
|
||||
id: id,
|
||||
name: name,
|
||||
quantity: 0,
|
||||
unit_price: price
|
||||
};
|
||||
this.items.push(new_item);
|
||||
this.add(new_item);
|
||||
},
|
||||
|
||||
/**
|
||||
* add an item to the basket.
|
||||
* This is called when the user click
|
||||
* on a button in the catalog (left side of the page)
|
||||
* @param id : int the id of the product to add
|
||||
* @param name : String the name of the product
|
||||
* @param price : number the unit price of the product
|
||||
*/
|
||||
add_from_catalog(id, name, price) {
|
||||
const item = this.items.find(e => e.id === id)
|
||||
if (item === undefined) {
|
||||
this.create_item(id, name, price);
|
||||
} else {
|
||||
// the user clicked on an item which is already in the basket
|
||||
this.add(item);
|
||||
}
|
||||
},
|
||||
}))
|
||||
})
|
73
eboutic/static/eboutic/js/makecommand.js
Normal file
73
eboutic/static/eboutic/js/makecommand.js
Normal file
@ -0,0 +1,73 @@
|
||||
document.addEventListener('alpine:init', () => {
|
||||
Alpine.store('bank_payment_enabled', false)
|
||||
|
||||
Alpine.store('billing_inputs', {
|
||||
data: JSON.parse(et_data)["data"],
|
||||
|
||||
async fill() {
|
||||
document.getElementById("bank-submit-button").disabled = true;
|
||||
const request = new Request(et_data_url, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
const res = await fetch(request);
|
||||
if (res.ok) {
|
||||
const json = await res.json();
|
||||
if (json["data"]) {
|
||||
this.data = json["data"];
|
||||
}
|
||||
document.getElementById("bank-submit-button").disabled = false;
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
Alpine.data('billing_infos', () => ({
|
||||
errors: [],
|
||||
successful: false,
|
||||
url: billing_info_exist ? edit_billing_info_url : create_billing_info_url,
|
||||
|
||||
async send_form() {
|
||||
const form = document.getElementById("billing_info_form");
|
||||
const submit_button = form.querySelector("input[type=submit]")
|
||||
submit_button.disabled = true;
|
||||
document.getElementById("bank-submit-button").disabled = true;
|
||||
this.successful = false
|
||||
|
||||
let payload = {};
|
||||
for (const elem of form.querySelectorAll("input")) {
|
||||
if (elem.type === "text" && elem.value) {
|
||||
payload[elem.name] = elem.value;
|
||||
}
|
||||
}
|
||||
const country = form.querySelector("select");
|
||||
if (country && country.value) {
|
||||
payload[country.name] = country.value;
|
||||
}
|
||||
const request = new Request(this.url, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': getCSRFToken(),
|
||||
},
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
const res = await fetch(request);
|
||||
const json = await res.json();
|
||||
if (json["errors"]) {
|
||||
this.errors = json["errors"];
|
||||
} else {
|
||||
this.errors = [];
|
||||
this.successful = true;
|
||||
this.url = edit_billing_info_url;
|
||||
Alpine.store("billing_inputs").fill();
|
||||
}
|
||||
submit_button.disabled = false;
|
||||
}
|
||||
}))
|
||||
})
|
||||
|
||||
|
@ -25,11 +25,13 @@
|
||||
<div id="basket">
|
||||
<h3>Panier</h3>
|
||||
{% if errors %}
|
||||
<div class="error-message">
|
||||
{% for error in errors %}
|
||||
<p>{{ error }}</p>
|
||||
{% endfor %}
|
||||
{% trans %}Your basket has been cleaned accordingly to those errors.{% endtrans %}
|
||||
<div class="alert alert-red">
|
||||
<div class="alert-main">
|
||||
{% for error in errors %}
|
||||
<p style="margin: 0">{{ error }}</p>
|
||||
{% endfor %}
|
||||
{% trans %}Your basket has been cleaned accordingly to those errors.{% endtrans %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<ul class="item-list">
|
||||
@ -64,7 +66,7 @@
|
||||
<i class="fa fa-trash"></i>
|
||||
{% trans %}Clear{% endtrans %}
|
||||
</button>
|
||||
<form method="post" action="{{ url('eboutic:command') }}">
|
||||
<form method="get" action="{{ url('eboutic:command') }}">
|
||||
{% csrf_token %}
|
||||
<button class="validate">
|
||||
<i class="fa fa-check"></i>
|
||||
@ -75,7 +77,7 @@
|
||||
</div>
|
||||
<div id="catalog">
|
||||
{% if not request.user.date_of_birth %}
|
||||
<div class="alert" x-data="{show_alert: true}" x-show="show_alert" x-transition>
|
||||
<div class="alert alert-red" x-data="{show_alert: true}" x-show="show_alert" x-transition>
|
||||
<span class="alert-main">
|
||||
{% trans %}You have not filled in your date of birth. As a result, you may not have access to all the products in the online shop. To fill in your date of birth, you can go to{% endtrans %}
|
||||
<a href="{{ url("core:user_edit", user_id=request.user.id) }}">
|
||||
|
@ -1,69 +1,143 @@
|
||||
{% extends "core/base.jinja" %}
|
||||
|
||||
{% block title %}
|
||||
{% trans %}Basket state{% endtrans %}
|
||||
{% trans %}Basket state{% endtrans %}
|
||||
{% endblock %}
|
||||
|
||||
{% block jquery_css %}
|
||||
{# Remove jquery css #}
|
||||
{% endblock %}
|
||||
|
||||
{% block additional_js %}
|
||||
<script src="{{ static('eboutic/js/makecommand.js') }}" defer></script>
|
||||
<script src="{{ static('core/js/alpinejs.min.js') }}" defer></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h3>{% trans %}Eboutic{% endtrans %}</h3>
|
||||
<h3>{% trans %}Eboutic{% endtrans %}</h3>
|
||||
|
||||
<div>
|
||||
<p>{% trans %}Basket: {% endtrans %}</p>
|
||||
<table>
|
||||
<thead>
|
||||
<div>
|
||||
<p>{% trans %}Basket: {% endtrans %}</p>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<td>Article</td>
|
||||
<td>Quantity</td>
|
||||
<td>Unit price</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for item in basket.items.all() %}
|
||||
<tr>
|
||||
<td>{{ item.product_name }}</td>
|
||||
<td>{{ item.quantity }}</td>
|
||||
<td>{{ item.product_unit_price }} €</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ item.product_name }}</td>
|
||||
<td>{{ item.quantity }}</td>
|
||||
<td>{{ item.product_unit_price }} €</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
<tbody>
|
||||
</table>
|
||||
<tbody>
|
||||
</table>
|
||||
|
||||
<p>
|
||||
<strong>{% trans %}Basket amount: {% endtrans %}{{ "%0.2f"|format(basket.get_total()) }} €</strong>
|
||||
|
||||
{% if customer_amount != None %}
|
||||
<br>
|
||||
{% trans %}Current account amount: {% endtrans %}<strong>{{ "%0.2f"|format(customer_amount) }} €</strong>
|
||||
|
||||
{% if not basket.contains_refilling_item %}
|
||||
<br>
|
||||
{% trans %}Remaining account amount: {% endtrans %}
|
||||
<strong>{{ "%0.2f"|format(customer_amount|float - basket.get_total()) }} €</strong>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</p>
|
||||
{% if settings.SITH_EBOUTIC_CB_ENABLED %}
|
||||
<form method="post" action="{{ settings.SITH_EBOUTIC_ET_URL }}">
|
||||
<p>
|
||||
{% for (field_name,field_value) in et_request.items() -%}
|
||||
<input type="hidden" name="{{ field_name }}" value="{{ field_value }}">
|
||||
{% endfor %}
|
||||
<input type="submit" value="{% trans %}Pay with credit card{% endtrans %}" />
|
||||
<strong>{% trans %}Basket amount: {% endtrans %}{{ "%0.2f"|format(basket.get_total()) }} €</strong>
|
||||
|
||||
{% if customer_amount != None %}
|
||||
<br>
|
||||
{% trans %}Current account amount: {% endtrans %}
|
||||
<strong>{{ "%0.2f"|format(customer_amount) }} €</strong>
|
||||
|
||||
{% if not basket.contains_refilling_item %}
|
||||
<br>
|
||||
{% trans %}Remaining account amount: {% endtrans %}
|
||||
<strong>{{ "%0.2f"|format(customer_amount|float - basket.get_total()) }} €</strong>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</p>
|
||||
</form>
|
||||
{% endif %}
|
||||
{% if basket.contains_refilling_item %}
|
||||
<p>{% trans %}AE account payment disabled because your basket contains refilling items.{% endtrans %}</p>
|
||||
{% else %}
|
||||
<form method="post" action="{{ url('eboutic:pay_with_sith') }}">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="pay_with_sith_account">
|
||||
<input type="submit" value="{% trans %}Pay with Sith account{% endtrans %}" />
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
<br>
|
||||
{% if settings.SITH_EBOUTIC_CB_ENABLED %}
|
||||
<div class="collapse" :class="{'shadow': collapsed}" x-data="{collapsed: false}" x-cloak>
|
||||
<div class="collapse-header clickable" @click="collapsed = !collapsed">
|
||||
<span class="collapse-header-text">
|
||||
{% trans %}Edit billing information{% endtrans %}
|
||||
</span>
|
||||
<span class="collapse-header-icon" :class="{'reverse': collapsed}">
|
||||
<i class="fa fa-caret-down"></i>
|
||||
</span>
|
||||
</div>
|
||||
<form class="collapse-body" id="billing_info_form" method="post"
|
||||
x-show="collapsed" x-data="billing_infos"
|
||||
x-transition.scale.origin.top
|
||||
@submit.prevent="send_form()">
|
||||
{% csrf_token %}
|
||||
{{ billing_form }}
|
||||
<br>
|
||||
<br>
|
||||
<div x-show="errors.length > 0" class="alert alert-red" x-transition>
|
||||
<div class="alert-main">
|
||||
<template x-for="error in errors">
|
||||
<div x-text="error.field + ' : ' + error.messages.join(', ')"></div>
|
||||
</template>
|
||||
</div>
|
||||
<div class="clickable" @click="errors = []">
|
||||
<i class="fa fa-close"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div x-show="successful" class="alert alert-green" x-transition>
|
||||
<div class="alert-main">
|
||||
Informations de facturation enregistrées
|
||||
</div>
|
||||
<div class="clickable" @click="successful = false">
|
||||
<i class="fa fa-close"></i>
|
||||
</div>
|
||||
</div>
|
||||
<input type="submit" class="btn btn-blue clickable"
|
||||
value="{% trans %}Validate{% endtrans %}">
|
||||
</form>
|
||||
</div>
|
||||
<br>
|
||||
{% if must_fill_billing_infos %}
|
||||
<p>
|
||||
<i>
|
||||
{% trans %}You must fill your billing infos if you want to pay with your credit
|
||||
card{% endtrans %}
|
||||
</i>
|
||||
</p>
|
||||
{% endif %}
|
||||
<form method="post" action="{{ settings.SITH_EBOUTIC_ET_URL }}" name="bank-pay-form">
|
||||
{% csrf_token %}
|
||||
<template x-data x-for="input in $store.billing_inputs.data">
|
||||
<input type="hidden" :name="input['key']" :value="input['value']">
|
||||
</template>
|
||||
<input type="submit" id="bank-submit-button"
|
||||
{% if must_fill_billing_infos %}disabled="disabled"{% endif %}
|
||||
value="{% trans %}Pay with credit card{% endtrans %}"/>
|
||||
</form>
|
||||
{% endif %}
|
||||
{% if basket.contains_refilling_item %}
|
||||
<p>{% trans %}AE account payment disabled because your basket contains refilling items.{% endtrans %}</p>
|
||||
{% else %}
|
||||
<form method="post" action="{{ url('eboutic:pay_with_sith') }}" name="sith-pay-form">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="pay_with_sith_account">
|
||||
<input type="submit" value="{% trans %}Pay with Sith account{% endtrans %}"/>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block script %}
|
||||
<script>
|
||||
const create_billing_info_url = '{{ url("counter:create_billing_info", user_id=request.user.id) }}'
|
||||
const edit_billing_info_url = '{{ url("counter:edit_billing_info", user_id=request.user.id) }}';
|
||||
const et_data_url = '{{ url("eboutic:et_data") }}'
|
||||
let billing_info_exist =
|
||||
{{ "true" if billing_infos else "false" }}
|
||||
|
||||
|
||||
{% if billing_infos %}
|
||||
const et_data = {{ billing_infos|tojson }}
|
||||
{% else %}
|
||||
const et_data = '{"data": []}'
|
||||
{% endif %}
|
||||
</script>
|
||||
{{ super() }}
|
||||
{% endblock %}
|
||||
|
||||
|
@ -24,7 +24,6 @@
|
||||
#
|
||||
import base64
|
||||
import json
|
||||
import re
|
||||
import urllib
|
||||
|
||||
from OpenSSL import crypto
|
||||
@ -40,18 +39,19 @@ from eboutic.models import Basket
|
||||
|
||||
|
||||
class EbouticTest(TestCase):
|
||||
def setUp(self):
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
call_command("populate")
|
||||
self.skia = User.objects.filter(username="skia").first()
|
||||
self.subscriber = User.objects.filter(username="subscriber").first()
|
||||
self.old_subscriber = User.objects.filter(username="old_subscriber").first()
|
||||
self.public = User.objects.filter(username="public").first()
|
||||
self.barbar = Product.objects.filter(code="BARB").first()
|
||||
self.refill = Product.objects.filter(code="15REFILL").first()
|
||||
self.cotis = Product.objects.filter(code="1SCOTIZ").first()
|
||||
self.eboutic = Counter.objects.filter(name="Eboutic").first()
|
||||
cls.barbar = Product.objects.filter(code="BARB").first()
|
||||
cls.refill = Product.objects.filter(code="15REFILL").first()
|
||||
cls.cotis = Product.objects.filter(code="1SCOTIZ").first()
|
||||
cls.eboutic = Counter.objects.filter(name="Eboutic").first()
|
||||
cls.skia = User.objects.filter(username="skia").first()
|
||||
cls.subscriber = User.objects.filter(username="subscriber").first()
|
||||
cls.old_subscriber = User.objects.filter(username="old_subscriber").first()
|
||||
cls.public = User.objects.filter(username="public").first()
|
||||
|
||||
def get_busy_basket(self, user):
|
||||
def get_busy_basket(self, user) -> Basket:
|
||||
"""
|
||||
Create and return a basket with 3 barbar and 1 cotis in it.
|
||||
Edit the client session to store the basket id in it
|
||||
@ -64,11 +64,11 @@ class EbouticTest(TestCase):
|
||||
basket.add_product(self.cotis)
|
||||
return basket
|
||||
|
||||
def generate_bank_valid_answer_from_page_content(self, content):
|
||||
content = str(content)
|
||||
basket_id = re.search(r"PBX_CMD\" value=\"(\d*)\"", content).group(1)
|
||||
amount = re.search(r"PBX_TOTAL\" value=\"(\d*)\"", content).group(1)
|
||||
query = "Amount=%s&BasketID=%s&Auto=42&Error=00000" % (amount, basket_id)
|
||||
def generate_bank_valid_answer(self) -> str:
|
||||
basket = Basket.from_session(self.client.session)
|
||||
basket_id = basket.id
|
||||
amount = int(basket.get_total() * 100)
|
||||
query = f"Amount={amount}&BasketID={basket_id}&Auto=42&Error=00000"
|
||||
with open("./eboutic/tests/private_key.pem") as f:
|
||||
PRIVKEY = f.read()
|
||||
with open("./eboutic/tests/public_key.pem") as f:
|
||||
@ -81,8 +81,7 @@ class EbouticTest(TestCase):
|
||||
query,
|
||||
urllib.parse.quote_plus(b64sig),
|
||||
)
|
||||
response = self.client.get(url)
|
||||
return response
|
||||
return url
|
||||
|
||||
def test_buy_with_sith_account(self):
|
||||
self.client.login(username="subscriber", password="plop")
|
||||
@ -102,7 +101,7 @@ class EbouticTest(TestCase):
|
||||
def test_buy_with_sith_account_no_money(self):
|
||||
self.client.login(username="subscriber", password="plop")
|
||||
basket = self.get_busy_basket(self.subscriber)
|
||||
initial = basket.get_total() - 1
|
||||
initial = basket.get_total() - 1 # just not enough to complete the sale
|
||||
self.subscriber.customer.amount = initial
|
||||
self.subscriber.customer.save()
|
||||
response = self.client.post(reverse("eboutic:pay_with_sith"))
|
||||
@ -122,7 +121,7 @@ class EbouticTest(TestCase):
|
||||
{"id": 2, "name": "Cotis 2 semestres", "quantity": 1, "unit_price": 28},
|
||||
{"id": 4, "name": "Barbar", "quantity": 3, "unit_price": 1.7}
|
||||
]"""
|
||||
response = self.client.post(reverse("eboutic:command"))
|
||||
response = self.client.get(reverse("eboutic:command"))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertInHTML(
|
||||
"<tr><td>Cotis 2 semestres</td><td>1</td><td>28.00 €</td></tr>",
|
||||
@ -146,7 +145,7 @@ class EbouticTest(TestCase):
|
||||
def test_submit_empty_basket(self):
|
||||
self.client.login(username="subscriber", password="plop")
|
||||
self.client.cookies["basket_items"] = "[]"
|
||||
response = self.client.post(reverse("eboutic:command"))
|
||||
response = self.client.get(reverse("eboutic:command"))
|
||||
self.assertRedirects(response, "/eboutic/")
|
||||
|
||||
def test_submit_invalid_basket(self):
|
||||
@ -157,7 +156,7 @@ class EbouticTest(TestCase):
|
||||
] = f"""[
|
||||
{{"id": {max_id + 1}, "name": "", "quantity": 1, "unit_price": 28}}
|
||||
]"""
|
||||
response = self.client.post(reverse("eboutic:command"))
|
||||
response = self.client.get(reverse("eboutic:command"))
|
||||
self.assertIn(
|
||||
'basket_items=""',
|
||||
self.client.cookies["basket_items"].OutputString(),
|
||||
@ -175,7 +174,7 @@ class EbouticTest(TestCase):
|
||||
] = """[
|
||||
{"id": 4, "name": "Barbar", "quantity": -1, "unit_price": 1.7}
|
||||
]"""
|
||||
response = self.client.post(reverse("eboutic:command"))
|
||||
response = self.client.get(reverse("eboutic:command"))
|
||||
self.assertRedirects(response, "/eboutic/")
|
||||
|
||||
def test_buy_subscribe_product_with_credit_card(self):
|
||||
@ -189,14 +188,14 @@ class EbouticTest(TestCase):
|
||||
] = """[
|
||||
{"id": 2, "name": "Cotis 2 semestres", "quantity": 1, "unit_price": 28}
|
||||
]"""
|
||||
response = self.client.post(reverse("eboutic:command"))
|
||||
response = self.client.get(reverse("eboutic:command"))
|
||||
self.assertInHTML(
|
||||
"<tr><td>Cotis 2 semestres</td><td>1</td><td>28.00 €</td></tr>",
|
||||
response.content.decode(),
|
||||
)
|
||||
basket = Basket.objects.get(id=self.client.session["basket_id"])
|
||||
self.assertEqual(basket.items.count(), 1)
|
||||
response = self.generate_bank_valid_answer_from_page_content(response.content)
|
||||
response = self.client.get(self.generate_bank_valid_answer())
|
||||
self.assertTrue(response.status_code == 200)
|
||||
self.assertTrue(response.content.decode("utf-8") == "Payment successful")
|
||||
|
||||
@ -215,9 +214,10 @@ class EbouticTest(TestCase):
|
||||
[{"id": 3, "name": "Rechargement 15 €", "quantity": 1, "unit_price": 15}]
|
||||
)
|
||||
initial_balance = self.subscriber.customer.amount
|
||||
response = self.client.post(reverse("eboutic:command"))
|
||||
self.client.get(reverse("eboutic:command"))
|
||||
|
||||
response = self.generate_bank_valid_answer_from_page_content(response.content)
|
||||
url = self.generate_bank_valid_answer()
|
||||
response = self.client.get(url)
|
||||
self.assertTrue(response.status_code == 200)
|
||||
self.assertTrue(response.content.decode() == "Payment successful")
|
||||
new_balance = Customer.objects.get(user=self.subscriber).amount
|
||||
@ -228,14 +228,15 @@ class EbouticTest(TestCase):
|
||||
self.client.cookies["basket_items"] = json.dumps(
|
||||
[{"id": 4, "name": "Barbar", "quantity": 1, "unit_price": 1.7}]
|
||||
)
|
||||
response = self.client.post(reverse("eboutic:command"))
|
||||
self.client.get(reverse("eboutic:command"))
|
||||
et_answer_url = self.generate_bank_valid_answer()
|
||||
self.client.cookies["basket_items"] = json.dumps(
|
||||
[ # alter basket
|
||||
{"id": 4, "name": "Barbar", "quantity": 3, "unit_price": 1.7}
|
||||
]
|
||||
)
|
||||
self.client.post(reverse("eboutic:command"))
|
||||
response = self.generate_bank_valid_answer_from_page_content(response.content)
|
||||
self.client.get(reverse("eboutic:command"))
|
||||
response = self.client.get(et_answer_url)
|
||||
self.assertEqual(response.status_code, 500)
|
||||
self.assertIn(
|
||||
"Basket processing failed with error: SuspiciousOperation('Basket total and amount do not match'",
|
||||
@ -247,8 +248,9 @@ class EbouticTest(TestCase):
|
||||
self.client.cookies["basket_items"] = json.dumps(
|
||||
[{"id": 4, "name": "Barbar", "quantity": 1, "unit_price": 1.7}]
|
||||
)
|
||||
response = self.client.post(reverse("eboutic:command"))
|
||||
response = self.generate_bank_valid_answer_from_page_content(response.content)
|
||||
self.client.get(reverse("eboutic:command"))
|
||||
et_answer_url = self.generate_bank_valid_answer()
|
||||
response = self.client.get(et_answer_url)
|
||||
self.assertTrue(response.status_code == 200)
|
||||
self.assertTrue(response.content.decode("utf-8") == "Payment successful")
|
||||
|
||||
|
@ -34,8 +34,9 @@ urlpatterns = [
|
||||
# Subscription views
|
||||
path("", eboutic_main, name="main"),
|
||||
path("command/", EbouticCommand.as_view(), name="command"),
|
||||
path("pay/", pay_with_sith, name="pay_with_sith"),
|
||||
path("pay/sith/", pay_with_sith, name="pay_with_sith"),
|
||||
path("pay/<res:result>/", payment_result, name="payment_result"),
|
||||
path("et_data/", e_transaction_data, name="et_data"),
|
||||
path(
|
||||
"et_autoanswer",
|
||||
EtransactionAutoAnswer.as_view(),
|
||||
|
121
eboutic/views.py
121
eboutic/views.py
@ -23,13 +23,10 @@
|
||||
#
|
||||
|
||||
import base64
|
||||
import hmac
|
||||
import json
|
||||
from collections import OrderedDict
|
||||
from datetime import datetime
|
||||
|
||||
import sentry_sdk
|
||||
|
||||
|
||||
from OpenSSL import crypto
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.decorators import login_required
|
||||
@ -41,7 +38,8 @@ from django.utils.decorators import method_decorator
|
||||
from django.views.decorators.http import require_GET, require_POST
|
||||
from django.views.generic import TemplateView, View
|
||||
|
||||
from counter.models import Customer, Counter, Selling
|
||||
from counter.forms import BillingInfoForm
|
||||
from counter.models import Customer, Counter, Product
|
||||
from eboutic.forms import BasketForm
|
||||
from eboutic.models import Basket, Invoice, InvoiceItem, get_eboutic_products
|
||||
|
||||
@ -85,11 +83,11 @@ class EbouticCommand(TemplateView):
|
||||
template_name = "eboutic/eboutic_makecommand.jinja"
|
||||
|
||||
@method_decorator(login_required)
|
||||
def get(self, request, *args, **kwargs):
|
||||
def post(self, request, *args, **kwargs):
|
||||
return redirect("eboutic:main")
|
||||
|
||||
@method_decorator(login_required)
|
||||
def post(self, request: HttpRequest, *args, **kwargs):
|
||||
def get(self, request: HttpRequest, *args, **kwargs):
|
||||
form = BasketForm(request)
|
||||
if not form.is_valid():
|
||||
request.session["errors"] = form.get_error_messages()
|
||||
@ -98,65 +96,56 @@ class EbouticCommand(TemplateView):
|
||||
res.set_cookie("basket_items", form.get_cleaned_cookie(), path="/eboutic")
|
||||
return res
|
||||
|
||||
if "basket_id" in request.session:
|
||||
basket, _ = Basket.objects.get_or_create(
|
||||
id=request.session["basket_id"], user=request.user
|
||||
)
|
||||
basket = Basket.from_session(request.session)
|
||||
if basket is not None:
|
||||
basket.clear()
|
||||
else:
|
||||
basket = Basket.objects.create(user=request.user)
|
||||
request.session["basket_id"] = basket.id
|
||||
request.session.modified = True
|
||||
|
||||
basket.save()
|
||||
eboutique = Counter.objects.get(type="EBOUTIC")
|
||||
for item in json.loads(request.COOKIES["basket_items"]):
|
||||
basket.add_product(
|
||||
eboutique.products.get(id=(item["id"])), item["quantity"]
|
||||
)
|
||||
request.session["basket_id"] = basket.id
|
||||
request.session.modified = True
|
||||
items = json.loads(request.COOKIES["basket_items"])
|
||||
items.sort(key=lambda item: item["id"])
|
||||
ids = [item["id"] for item in items]
|
||||
quantities = [item["quantity"] for item in items]
|
||||
products = Product.objects.filter(id__in=ids)
|
||||
for product, qty in zip(products, quantities):
|
||||
basket.add_product(product, qty)
|
||||
kwargs["basket"] = basket
|
||||
return self.render_to_response(self.get_context_data(**kwargs))
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
kwargs = super(EbouticCommand, self).get_context_data(**kwargs)
|
||||
# basket is already in kwargs when the method is called
|
||||
default_billing_info = None
|
||||
if hasattr(self.request.user, "customer"):
|
||||
kwargs["customer_amount"] = self.request.user.customer.amount
|
||||
customer = self.request.user.customer
|
||||
kwargs["customer_amount"] = customer.amount
|
||||
if hasattr(customer, "billing_infos"):
|
||||
default_billing_info = customer.billing_infos
|
||||
else:
|
||||
kwargs["customer_amount"] = None
|
||||
kwargs["et_request"] = OrderedDict()
|
||||
kwargs["et_request"]["PBX_SITE"] = settings.SITH_EBOUTIC_PBX_SITE
|
||||
kwargs["et_request"]["PBX_RANG"] = settings.SITH_EBOUTIC_PBX_RANG
|
||||
kwargs["et_request"]["PBX_IDENTIFIANT"] = settings.SITH_EBOUTIC_PBX_IDENTIFIANT
|
||||
kwargs["et_request"]["PBX_TOTAL"] = int(kwargs["basket"].get_total() * 100)
|
||||
kwargs["et_request"][
|
||||
"PBX_DEVISE"
|
||||
] = 978 # This is Euro. ET support only this value anyway
|
||||
kwargs["et_request"]["PBX_CMD"] = kwargs["basket"].id
|
||||
kwargs["et_request"]["PBX_PORTEUR"] = kwargs["basket"].user.email
|
||||
kwargs["et_request"]["PBX_RETOUR"] = "Amount:M;BasketID:R;Auto:A;Error:E;Sig:K"
|
||||
kwargs["et_request"]["PBX_HASH"] = "SHA512"
|
||||
kwargs["et_request"]["PBX_TYPEPAIEMENT"] = "CARTE"
|
||||
kwargs["et_request"]["PBX_TYPECARTE"] = "CB"
|
||||
kwargs["et_request"]["PBX_TIME"] = str(
|
||||
datetime.now().replace(microsecond=0).isoformat("T")
|
||||
)
|
||||
kwargs["et_request"]["PBX_HMAC"] = (
|
||||
hmac.new(
|
||||
settings.SITH_EBOUTIC_HMAC_KEY,
|
||||
bytes(
|
||||
"&".join(
|
||||
["%s=%s" % (k, v) for k, v in kwargs["et_request"].items()]
|
||||
),
|
||||
"utf-8",
|
||||
),
|
||||
"sha512",
|
||||
)
|
||||
.hexdigest()
|
||||
.upper()
|
||||
)
|
||||
kwargs["must_fill_billing_infos"] = default_billing_info is None
|
||||
if not kwargs["must_fill_billing_infos"]:
|
||||
# the user has already filled its billing_infos, thus we can
|
||||
# get it without expecting an error
|
||||
data = kwargs["basket"].get_e_transaction_data()
|
||||
data = {"data": [{"key": key, "value": val} for key, val in data]}
|
||||
kwargs["billing_infos"] = json.dumps(data)
|
||||
kwargs["billing_form"] = BillingInfoForm(instance=default_billing_info)
|
||||
return kwargs
|
||||
|
||||
|
||||
@login_required
|
||||
@require_GET
|
||||
def e_transaction_data(request):
|
||||
basket = Basket.from_session(request.session)
|
||||
if basket is None:
|
||||
return HttpResponse(status=404, content=json.dumps({"data": []}))
|
||||
data = basket.get_e_transaction_data()
|
||||
data = {"data": [{"key": key, "value": val} for key, val in data]}
|
||||
return HttpResponse(status=200, content=json.dumps(data))
|
||||
|
||||
|
||||
@login_required
|
||||
@require_POST
|
||||
def pay_with_sith(request):
|
||||
@ -171,24 +160,14 @@ def pay_with_sith(request):
|
||||
res = redirect("eboutic:payment_result", "failure")
|
||||
else:
|
||||
eboutic = Counter.objects.filter(type="EBOUTIC").first()
|
||||
sales = basket.generate_sales(eboutic, c.user, "SITH_ACCOUNT")
|
||||
try:
|
||||
with transaction.atomic():
|
||||
for it in basket.items.all():
|
||||
product = eboutic.products.get(id=it.product_id)
|
||||
Selling(
|
||||
label=it.product_name,
|
||||
counter=eboutic,
|
||||
club=product.club,
|
||||
product=product,
|
||||
seller=c.user,
|
||||
customer=c,
|
||||
unit_price=it.product_unit_price,
|
||||
quantity=it.quantity,
|
||||
payment_method="SITH_ACCOUNT",
|
||||
).save()
|
||||
for sale in sales:
|
||||
sale.save()
|
||||
basket.delete()
|
||||
request.session.pop("basket_id", None)
|
||||
res = redirect("eboutic:payment_result", "success")
|
||||
request.session.pop("basket_id", None)
|
||||
res = redirect("eboutic:payment_result", "success")
|
||||
except DatabaseError as e:
|
||||
with sentry_sdk.push_scope() as scope:
|
||||
scope.user = {"username": request.user.username}
|
||||
@ -205,12 +184,8 @@ class EtransactionAutoAnswer(View):
|
||||
# Response documentation http://www1.paybox.com/espace-integrateur-documentation
|
||||
# /la-solution-paybox-system/gestion-de-la-reponse/
|
||||
def get(self, request, *args, **kwargs):
|
||||
if (
|
||||
not "Amount" in request.GET.keys()
|
||||
or not "BasketID" in request.GET.keys()
|
||||
or not "Error" in request.GET.keys()
|
||||
or not "Sig" in request.GET.keys()
|
||||
):
|
||||
required = {"Amount", "BasketID", "Error", "Sig"}
|
||||
if not required.issubset(set(request.GET.keys())):
|
||||
return HttpResponse("Bad arguments", status=400)
|
||||
key = crypto.load_publickey(crypto.FILETYPE_PEM, settings.SITH_EBOUTIC_PUB_KEY)
|
||||
cert = crypto.X509()
|
||||
|
Reference in New Issue
Block a user