Add sftp test server code
This commit is contained in:
parent
f8c6e135e1
commit
c89914107f
|
|
@ -278,10 +278,11 @@ cypress/test-reports
|
||||||
|
|
||||||
git-crypt-encrypted-files-check.txt
|
git-crypt-encrypted-files-check.txt
|
||||||
|
|
||||||
|
|
||||||
/server/vbv_lernwelt/static/css/tailwind.css
|
/server/vbv_lernwelt/static/css/tailwind.css
|
||||||
/server/vbv_lernwelt/static/vue/
|
/server/vbv_lernwelt/static/vue/
|
||||||
/server/vbv_lernwelt/static/storybook
|
/server/vbv_lernwelt/static/storybook
|
||||||
/server/vbv_lernwelt/templates/vue/index.html
|
/server/vbv_lernwelt/templates/vue/index.html
|
||||||
/server/vbv_lernwelt/media
|
/server/vbv_lernwelt/media
|
||||||
/client/src/gql/dist/minifiedSchema.json
|
/client/src/gql/dist/minifiedSchema.json
|
||||||
|
|
||||||
|
/sftptest/
|
||||||
|
|
|
||||||
|
|
@ -682,10 +682,13 @@ else:
|
||||||
"DATATRANS_PAY_URL", default="https://pay.sandbox.datatrans.com"
|
"DATATRANS_PAY_URL", default="https://pay.sandbox.datatrans.com"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Only for debugging the webhook (locally)
|
|
||||||
DATATRANS_DEBUG_WEBHOOK_OVERWRITE = env(
|
# default settings for python sftpserver test-server
|
||||||
"DATATRANS_DEBUG_WEBHOOK_OVERWRITE", default=None
|
ABACUS_EXPORT_SFTP_HOST = env("ABACUS_EXPORT_SFTP_HOST", default="localhost")
|
||||||
)
|
ABACUS_EXPORT_SFTP_PORT = env("ABACUS_EXPORT_SFTP_PORT", default="3373")
|
||||||
|
ABACUS_EXPORT_SFTP_USERNAME = env("ABACUS_EXPORT_SFTP_USERNAME", default="admin")
|
||||||
|
ABACUS_EXPORT_SFTP_PASSWORD = env("ABACUS_EXPORT_SFTP_PASSWORD", default="admin")
|
||||||
|
|
||||||
|
|
||||||
# S3 BUCKET CONFIGURATION
|
# S3 BUCKET CONFIGURATION
|
||||||
FILE_UPLOAD_STORAGE = env("FILE_UPLOAD_STORAGE", default="s3") # local | s3
|
FILE_UPLOAD_STORAGE = env("FILE_UPLOAD_STORAGE", default="s3") # local | s3
|
||||||
|
|
|
||||||
|
|
@ -14,8 +14,8 @@ from .base import * # noqa
|
||||||
# https://docs.djangoproject.com/en/dev/ref/settings/#secret-key
|
# https://docs.djangoproject.com/en/dev/ref/settings/#secret-key
|
||||||
DATABASES["default"]["NAME"] = "vbv_lernwelt_cypress"
|
DATABASES["default"]["NAME"] = "vbv_lernwelt_cypress"
|
||||||
|
|
||||||
DATATRANS_API_ENDPOINT = 'http://localhost:8001/server/fakeapi/datatrans/api'
|
DATATRANS_API_ENDPOINT = "http://localhost:8001/server/fakeapi/datatrans/api"
|
||||||
DATATRANS_PAY_URL = 'http://localhost:8001/server/fakeapi/datatrans/pay'
|
DATATRANS_PAY_URL = "http://localhost:8001/server/fakeapi/datatrans/pay"
|
||||||
|
|
||||||
# EMAIL
|
# EMAIL
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
|
@ -64,14 +64,14 @@ from vbv_lernwelt.importer.views import (
|
||||||
)
|
)
|
||||||
from vbv_lernwelt.media_files.views import user_image
|
from vbv_lernwelt.media_files.views import user_image
|
||||||
from vbv_lernwelt.notify.views import email_notification_settings
|
from vbv_lernwelt.notify.views import email_notification_settings
|
||||||
from wagtail import urls as wagtail_urls
|
|
||||||
from wagtail.admin import urls as wagtailadmin_urls
|
|
||||||
from wagtail.documents import urls as media_library_urls
|
|
||||||
|
|
||||||
from vbv_lernwelt.shop.datatrans_fake_server import (
|
from vbv_lernwelt.shop.datatrans_fake_server import (
|
||||||
fake_datatrans_api_view,
|
fake_datatrans_api_view,
|
||||||
fake_datatrans_pay_view,
|
fake_datatrans_pay_view,
|
||||||
)
|
)
|
||||||
|
from wagtail import urls as wagtail_urls
|
||||||
|
from wagtail.admin import urls as wagtailadmin_urls
|
||||||
|
from wagtail.documents import urls as media_library_urls
|
||||||
|
|
||||||
|
|
||||||
class SignedIntConverter(IntConverter):
|
class SignedIntConverter(IntConverter):
|
||||||
|
|
|
||||||
|
|
@ -41,3 +41,6 @@ truffleHog
|
||||||
|
|
||||||
# deployement and CI
|
# deployement and CI
|
||||||
git+https://github.com/iterativ/Caprover-API.git@5013f8fc929e8e3281b9d609e968a782e8e99530
|
git+https://github.com/iterativ/Caprover-API.git@5013f8fc929e8e3281b9d609e968a782e8e99530
|
||||||
|
|
||||||
|
# sftpserver for tests
|
||||||
|
git+https://github.com/lonetwin/sftpserver.git@1d16896d3f0f90d63d1caaf4e199f2a9dde6456f
|
||||||
|
|
|
||||||
|
|
@ -341,7 +341,9 @@ packaging==23.1
|
||||||
# pytest
|
# pytest
|
||||||
# pytest-sugar
|
# pytest-sugar
|
||||||
paramiko==3.3.1
|
paramiko==3.3.1
|
||||||
# via -r requirements.in
|
# via
|
||||||
|
# -r requirements.in
|
||||||
|
# sftpserver
|
||||||
parso==0.8.3
|
parso==0.8.3
|
||||||
# via jedi
|
# via jedi
|
||||||
pathspec==0.11.2
|
pathspec==0.11.2
|
||||||
|
|
@ -397,9 +399,7 @@ pyflakes==3.1.0
|
||||||
pygments==2.16.1
|
pygments==2.16.1
|
||||||
# via ipython
|
# via ipython
|
||||||
pyjwt[crypto]==2.8.0
|
pyjwt[crypto]==2.8.0
|
||||||
# via
|
# via msal
|
||||||
# msal
|
|
||||||
# pyjwt
|
|
||||||
pylint==2.17.5
|
pylint==2.17.5
|
||||||
# via
|
# via
|
||||||
# pylint-django
|
# pylint-django
|
||||||
|
|
@ -480,6 +480,8 @@ sendgrid==6.10.0
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
sentry-sdk==1.29.2
|
sentry-sdk==1.29.2
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
|
sftpserver @ git+https://github.com/lonetwin/sftpserver.git@1d16896d3f0f90d63d1caaf4e199f2a9dde6456f
|
||||||
|
# via -r requirements-dev.in
|
||||||
six==1.16.0
|
six==1.16.0
|
||||||
# via
|
# via
|
||||||
# asttokens
|
# asttokens
|
||||||
|
|
@ -621,9 +623,7 @@ wheel==0.41.1
|
||||||
whitenoise[brotli]==6.5.0
|
whitenoise[brotli]==6.5.0
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
willow[heif]==1.6.1
|
willow[heif]==1.6.1
|
||||||
# via
|
# via wagtail
|
||||||
# wagtail
|
|
||||||
# willow
|
|
||||||
wrapt==1.15.0
|
wrapt==1.15.0
|
||||||
# via astroid
|
# via astroid
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -107,9 +107,10 @@ def fake_datatrans_pay_view(request, api_url=""):
|
||||||
def call_transaction_complete_webhook(
|
def call_transaction_complete_webhook(
|
||||||
webhook_url, transaction_id, datatrans_status="settled"
|
webhook_url, transaction_id, datatrans_status="settled"
|
||||||
):
|
):
|
||||||
import requests
|
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
|
|
||||||
payload = {
|
payload = {
|
||||||
|
|
@ -119,14 +120,14 @@ def fake_datatrans_pay_view(request, api_url=""):
|
||||||
key_hex_bytes = bytes.fromhex(settings.DATATRANS_HMAC_KEY)
|
key_hex_bytes = bytes.fromhex(settings.DATATRANS_HMAC_KEY)
|
||||||
|
|
||||||
# Create sign with timestamp and payload
|
# Create sign with timestamp and payload
|
||||||
sign = hmac.new(key_hex_bytes, bytes(str(1) + json.dumps(payload), "utf-8"), hashlib.sha256)
|
sign = hmac.new(
|
||||||
|
key_hex_bytes, bytes(str(1) + json.dumps(payload), "utf-8"), hashlib.sha256
|
||||||
|
)
|
||||||
|
|
||||||
response = requests.post(
|
response = requests.post(
|
||||||
url=webhook_url,
|
url=webhook_url,
|
||||||
json=payload,
|
json=payload,
|
||||||
headers={
|
headers={"Datatrans-Signature": f"t=1,s0={sign.hexdigest()}"},
|
||||||
"Datatrans-Signature": f"t=1,s0={sign.hexdigest()}"
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
print(response)
|
print(response)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,47 +1,28 @@
|
||||||
import datetime
|
import datetime
|
||||||
from uuid import uuid4
|
from io import BytesIO
|
||||||
from xml.dom import minidom
|
from xml.dom import minidom
|
||||||
from xml.etree.ElementTree import Element, SubElement, tostring
|
from xml.etree.ElementTree import Element, SubElement, tostring
|
||||||
|
|
||||||
from vbv_lernwelt.shop.invoice.creator import InvoiceCreator, Item
|
import structlog
|
||||||
from vbv_lernwelt.shop.invoice.repositories import InvoiceRepository
|
from django.conf import settings
|
||||||
|
from paramiko.client import AutoAddPolicy, SSHClient
|
||||||
|
|
||||||
from vbv_lernwelt.shop.models import CheckoutInformation
|
from vbv_lernwelt.shop.models import CheckoutInformation
|
||||||
|
|
||||||
|
|
||||||
class AbacusInvoiceCreator(InvoiceCreator):
|
logger = structlog.get_logger(__name__)
|
||||||
def __init__(self, repository: InvoiceRepository):
|
|
||||||
self.repository = repository
|
|
||||||
|
|
||||||
def create_invoice(
|
|
||||||
self,
|
|
||||||
checkout_information: CheckoutInformation,
|
|
||||||
filename: str = None,
|
|
||||||
):
|
|
||||||
customer_number = checkout_information.transaction_id
|
|
||||||
order_date = checkout_information.created_at.date()
|
|
||||||
reference_purchase_order = str(checkout_information.id)
|
|
||||||
unic_id = checkout_information.transaction_id
|
|
||||||
|
|
||||||
items = [
|
def abacus_ssh_upload(checkout_information: CheckoutInformation):
|
||||||
Item(
|
invoice_xml_filename, invoice_xml_content = create_invoice_xml(checkout_information)
|
||||||
product_number=checkout_information.product_sku,
|
customer_xml_filename, customer_xml_content = create_customer_xml(
|
||||||
quantity=1,
|
checkout_information
|
||||||
description=checkout_information.product_description,
|
)
|
||||||
)
|
|
||||||
]
|
|
||||||
|
|
||||||
invoice = self.render_invoice_xml(
|
abacus_ssh_upload_invoice(
|
||||||
customer_number,
|
customer_xml_filename, customer_xml_content, folder="debitor"
|
||||||
order_date,
|
)
|
||||||
reference_purchase_order,
|
abacus_ssh_upload_invoice(invoice_xml_filename, invoice_xml_content, folder="order")
|
||||||
unic_id,
|
|
||||||
items,
|
|
||||||
)
|
|
||||||
|
|
||||||
if filename is None:
|
|
||||||
filename = f"vbv-vv-{uuid4().hex}.xml"
|
|
||||||
|
|
||||||
self.repository.upload_invoice(invoice, filename)
|
|
||||||
|
|
||||||
|
|
||||||
def create_invoice_xml(checkout_information: CheckoutInformation):
|
def create_invoice_xml(checkout_information: CheckoutInformation):
|
||||||
|
|
@ -223,3 +204,33 @@ def create_xml_string(container: Element, encoding: str = "utf-8") -> str:
|
||||||
indent=" ", encoding=encoding
|
indent=" ", encoding=encoding
|
||||||
)
|
)
|
||||||
return xml_pretty_str.decode(encoding)
|
return xml_pretty_str.decode(encoding)
|
||||||
|
|
||||||
|
|
||||||
|
def abacus_ssh_upload_invoice(
|
||||||
|
filename: str, content_xml: str, folder: str = ""
|
||||||
|
) -> None:
|
||||||
|
invoice_io = BytesIO(content_xml.encode("utf-8"))
|
||||||
|
ssh_client = SSHClient()
|
||||||
|
|
||||||
|
print(invoice_io)
|
||||||
|
|
||||||
|
try:
|
||||||
|
ssh_client.set_missing_host_key_policy(AutoAddPolicy())
|
||||||
|
ssh_client.connect(
|
||||||
|
hostname=settings.ABACUS_EXPORT_SFTP_HOST,
|
||||||
|
port=settings.ABACUS_EXPORT_SFTP_PORT,
|
||||||
|
username=settings.ABACUS_EXPORT_SFTP_USERNAME,
|
||||||
|
password=settings.ABACUS_EXPORT_SFTP_PASSWORD,
|
||||||
|
)
|
||||||
|
|
||||||
|
with ssh_client.open_sftp() as sftp_client:
|
||||||
|
path = filename
|
||||||
|
if folder:
|
||||||
|
path = f"{folder}/{filename}"
|
||||||
|
|
||||||
|
sftp_client.putfo(invoice_io, path)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error("Could not upload invoice", exc_info=e)
|
||||||
|
finally:
|
||||||
|
ssh_client.close()
|
||||||
|
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
from abc import ABC, abstractmethod
|
|
||||||
from dataclasses import dataclass
|
|
||||||
|
|
||||||
from vbv_lernwelt.shop.models import CheckoutInformation
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class Item:
|
|
||||||
product_number: str
|
|
||||||
quantity: int
|
|
||||||
description: str
|
|
||||||
|
|
||||||
|
|
||||||
class InvoiceCreator(ABC):
|
|
||||||
@abstractmethod
|
|
||||||
def create_invoice(
|
|
||||||
self,
|
|
||||||
checkout_information: CheckoutInformation,
|
|
||||||
filename: str = None,
|
|
||||||
):
|
|
||||||
pass
|
|
||||||
|
|
@ -1,44 +0,0 @@
|
||||||
from abc import ABC, abstractmethod
|
|
||||||
|
|
||||||
import structlog
|
|
||||||
|
|
||||||
logger = structlog.get_logger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class InvoiceRepository(ABC):
|
|
||||||
@abstractmethod
|
|
||||||
def upload_invoice(self, invoice: str, filename: str):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class SFTPInvoiceRepository(InvoiceRepository):
|
|
||||||
def __init__(self, hostname: str, username: str, password: str, port: int = 22):
|
|
||||||
self.hostname = hostname
|
|
||||||
self.username = username
|
|
||||||
self.password = password
|
|
||||||
self.port = port
|
|
||||||
|
|
||||||
def upload_invoice(self, invoice: str, filename: str) -> None:
|
|
||||||
from io import BytesIO
|
|
||||||
|
|
||||||
from paramiko import AutoAddPolicy, SSHClient
|
|
||||||
|
|
||||||
invoice_io = BytesIO(invoice.encode("utf-8"))
|
|
||||||
ssh_client = SSHClient()
|
|
||||||
|
|
||||||
try:
|
|
||||||
ssh_client.set_missing_host_key_policy(AutoAddPolicy())
|
|
||||||
ssh_client.connect(
|
|
||||||
self.hostname,
|
|
||||||
port=self.port,
|
|
||||||
username=self.username,
|
|
||||||
password=self.password,
|
|
||||||
)
|
|
||||||
|
|
||||||
with ssh_client.open_sftp() as sftp_client:
|
|
||||||
sftp_client.putfo(invoice_io, f"uploads/{filename}")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error("Could not upload invoice", exc_info=e)
|
|
||||||
finally:
|
|
||||||
ssh_client.close()
|
|
||||||
|
|
@ -105,6 +105,6 @@ class Migration(migrations.Migration):
|
||||||
name="old_company_country",
|
name="old_company_country",
|
||||||
),
|
),
|
||||||
migrations.DeleteModel(
|
migrations.DeleteModel(
|
||||||
name='BillingAddress',
|
name="BillingAddress",
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -53,13 +53,6 @@ def init_datatrans_transaction(
|
||||||
redirect_url_cancel: str,
|
redirect_url_cancel: str,
|
||||||
webhook_url: str,
|
webhook_url: str,
|
||||||
):
|
):
|
||||||
if overwrite := settings.DATATRANS_DEBUG_WEBHOOK_OVERWRITE:
|
|
||||||
logger.warning(
|
|
||||||
"APPLYING DEBUG DATATRANS WEBHOOK OVERWRITE!",
|
|
||||||
webhook_url=overwrite,
|
|
||||||
)
|
|
||||||
webhook_url = overwrite
|
|
||||||
|
|
||||||
payload = {
|
payload = {
|
||||||
# We use autoSettle=True, so that we don't have to settle the transaction:
|
# We use autoSettle=True, so that we don't have to settle the transaction:
|
||||||
# -> Be aware that autoSettle has implications of the possible transaction states
|
# -> Be aware that autoSettle has implications of the possible transaction states
|
||||||
|
|
@ -77,8 +70,8 @@ def init_datatrans_transaction(
|
||||||
}
|
}
|
||||||
|
|
||||||
# add testing configuration data
|
# add testing configuration data
|
||||||
if 'fakeapi' in settings.DATATRANS_API_ENDPOINT:
|
if "fakeapi" in settings.DATATRANS_API_ENDPOINT:
|
||||||
payload['user_id'] = str(user.id)
|
payload["user_id"] = str(user.id)
|
||||||
|
|
||||||
logger.info("Initiating transaction", payload=payload)
|
logger.info("Initiating transaction", payload=payload)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# set location to script directory
|
||||||
|
cd "${0%/*}"
|
||||||
|
|
||||||
|
# start python sftp test server (for abacus exports)
|
||||||
|
rm -rf sftptest
|
||||||
|
mkdir -p sftptest/debitor
|
||||||
|
mkdir -p sftptest/order
|
||||||
|
(cd sftptest && sftpserver -p 3373)
|
||||||
Loading…
Reference in New Issue