# Datatrans - Proof of Concept
## Setup manual steps
- `HMAC_KEY`: https://admin.sandbox.datatrans.com/MerchSecurAdmin.jsp
- `BASIC_AUTH`: `echo -n "{merchantid}:{password}" | base64` (`merchantid`: `11xxxxxx`): https://admin.sandbox.datatrans.com/MenuDispatch.jsp?main=1&sub=4
## Links
- https://admin.sandbox.datatrans.com
- https://api-reference.datatrans.ch/#section/Idempotency
- https://docs.datatrans.ch/docs/redirect-lightbox#section-initializing-transactions
## Code
Simple example of the payment flow with Datatrans:
```python
from flask import Flask, request, render_template_string, jsonify, abort
import uuid
import hmac
import hashlib
import requests
import os
app = Flask(__name__)
if "HMAC_KEY" not in os.environ:
exit("Please set the HMAC_KEY environment variable.")
if "BASIC_AUTH" not in os.environ:
exit("Please set the BASIC_AUTH environment variable.")
# https://admin.sandbox.datatrans.com/MerchSecurAdmin.jsp
HMAC_KEY = os.environ["HMAC_KEY"]
BASIC_AUTH = os.environ["BASIC_AUTH"]
API_ENDPOINT = "https://api.sandbox.datatrans.com/v1/transactions"
LIGHTBOX_PAGE = """
"""
SUCCESS_PAGE = "Payment Success
"
ERROR_PAGE = "Payment Error
"
CANCEL_PAGE = "Payment Cancelled
"
# TODO: There is now way to test this locally, so we need to use ngrok (?)
BASE_URL = "https://89d3-2a02-21b4-9679-d800-ac5f-489-e9f6-694e.ngrok-free.app"
@app.route("/success", methods=["GET"])
def success():
return render_template_string(SUCCESS_PAGE)
@app.route("/error", methods=["GET"])
def error():
return render_template_string(ERROR_PAGE)
@app.route("/cancel", methods=["GET"])
def cancel():
return render_template_string(CANCEL_PAGE)
@app.route("/init_transaction", methods=["GET"])
def init_transaction():
# TODO
# for debugging, it might be handy to know the
# user who initiated the transaction
refno = uuid.uuid4().hex
# TODO
# The language of user
language = "en"
# Transaction payload
payload = {
"currency": "CHF",
"refno": refno,
"amount": 10_00, # 10 CHF
"autoSettle": True,
"language": language,
"redirect": {
"successUrl": f"{BASE_URL}/success",
"errorUrl": f"{BASE_URL}/error",
"cancelUrl": f"{BASE_URL}/cancel",
},
"webhook": {
"url": f"{BASE_URL}/webhook",
},
}
# Headers
headers = {
"Authorization": f"Basic {BASIC_AUTH}",
"Content-Type": "application/json",
}
# 1. USING LIGHTBOX
response = requests.post(API_ENDPOINT, json=payload, headers=headers)
if response.ok:
transaction_id = response.json().get("transactionId")
return render_template_string(LIGHTBOX_PAGE, transaction_id=transaction_id)
else:
return (
jsonify(
{"error": "Failed to initiate transaction", "details": response.text}
),
response.status_code,
)
# 2. USING REDIRECT
# # Send POST request to Datatrans API
# response = requests.post(url, json=payload, headers=headers)
# if response.ok:
# transaction_id = response.json().get('transactionId')
# payment_url = f'https://pay.sandbox.datatrans.com/v1/start/{transaction_id}'
# return redirect(payment_url)
# else:
# # Return error message
# return jsonify({"error": "Failed to initiate transaction", "details": response.text}), response.status_code
@app.route("/webhook", methods=["POST"])
def webhook():
"""
Checks the Datatrans-Signature header of the incoming request and validates the signature:
https://api-reference.datatrans.ch/#section/Webhook/Webhook-signing
"""
# TODO Check the state here too!
hmac_key = HMAC_KEY
def calculate_signature(key: str, timestamp: str, payload: str) -> str:
key_bytes = bytes.fromhex(key)
signing_data = f"{timestamp}{payload}".encode("utf-8")
hmac_obj = hmac.new(key_bytes, signing_data, hashlib.sha256)
return hmac_obj.hexdigest()
# Header format:
# Datatrans-Signature: t={{timestamp}},s0={{signature}}
datatrans_signature = request.headers.get("Datatrans-Signature", "")
try:
parts = datatrans_signature.split(",")
timestamp = parts[0].split("=")[1]
received_signature = parts[1].split("=")[1]
calculated_signature = calculate_signature(
hmac_key, timestamp, request.data.decode("utf-8")
)
if calculated_signature == received_signature:
return "Signature validated.", 200
else:
abort(400, "Invalid signature.")
except (IndexError, ValueError):
abort(400, "Invalid Datatrans-Signature header.")
if __name__ == "__main__":
app.run(debug=True, host="0.0.0.0", port=5500)
```