# 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) ```