diff --git a/README.md b/README.md index f7f53570f..e34340b5a 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,18 @@ The code consists of multiple services. Each service is located in a separate fo There is also a `utils` folder that contains some helper code or specifications that are used by multiple services. Check the `utils` folder for more information. +### Architecture + +The following diagrams give a high-level view of the system and how the services interact. + +**System overview** + +![System diagram](./system_diagram.png) + +**Service architecture** + +![Service architecture diagram](./architecture_diagram.png) + ### Running the code with Docker Compose [recommended] To run the code, you need to clone this repository, make sure you have Docker and Docker Compose installed, and run the following command in the root folder of the repository: diff --git a/architecture_diagram.png b/architecture_diagram.png new file mode 100644 index 000000000..2071d8908 Binary files /dev/null and b/architecture_diagram.png differ diff --git a/docker-compose.yaml b/docker-compose.yaml index b4a60a537..e4818a9c8 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -13,6 +13,7 @@ services: volumes: # Mount the frontend directory - ./frontend/src:/usr/share/nginx/html + orchestrator: build: # Use the current directory as the build context @@ -35,12 +36,13 @@ services: - ./utils:/app/utils # Mount the orchestrator/src directory in the current directory to the /app/orchestrator/src directory in the container - ./orchestrator/src:/app/orchestrator/src + fraud_detection: build: # Use the current directory as the build context # This allows us to access the files in the current directory inside the Dockerfile context: ./ - # Use the Dockerfile in the fraud_detection directorys + # Use the Dockerfile in the fraud_detection directory dockerfile: ./fraud_detection/Dockerfile ports: # Expose port 50051 on the host, and map port 50051 of the container to port 50051 on the host @@ -56,4 +58,37 @@ services: # Mount the utils directory in the current directory to the /app/utils directory in the container - ./utils:/app/utils # Mount the fraud_detection/src directory in the current directory to the /app/fraud_detection/src directory in the container - - ./fraud_detection/src:/app/fraud_detection/src \ No newline at end of file + - ./fraud_detection/src:/app/fraud_detection/src + + transaction_verification: + build: + # Use the current directory as the build context + context: ./ + # Use the Dockerfile in the transaction_verification directory + dockerfile: ./transaction_verification/Dockerfile + ports: + # Expose port 50052 on the host, and map port 50052 of the container to port 50052 on the host + - 50052:50052 + environment: + # Pass the environment variables to the container + - PYTHONUNBUFFERED=TRUE + # The PYTHONFILE environment variable specifies the absolute entry point of the application + - PYTHONFILE=/app/transaction_verification/src/app.py + volumes: + # Mount the utils directory in the current directory to the /app/utils directory in the container + - ./utils:/app/utils + # Mount the transaction_verification/src directory in the current directory to the container + - ./transaction_verification/src:/app/transaction_verification/src + + suggestions: + build: + context: ./ + dockerfile: ./suggestions/Dockerfile + ports: + - 50053:50053 + environment: + - PYTHONUNBUFFERED=TRUE + - PYTHONFILE=/app/suggestions/src/app.py + volumes: + - ./utils:/app/utils + - ./suggestions/src:/app/suggestions/src \ No newline at end of file diff --git a/fraud_detection/src/app.py b/fraud_detection/src/app.py index b2f1d2fce..ecee0fad5 100644 --- a/fraud_detection/src/app.py +++ b/fraud_detection/src/app.py @@ -1,45 +1,89 @@ import sys import os +from concurrent import futures # This set of lines are needed to import the gRPC stubs. -# The path of the stubs is relative to the current file, or absolute inside the container. -# Change these lines only if strictly needed. FILE = __file__ if '__file__' in globals() else os.getenv("PYTHONFILE", "") -fraud_detection_grpc_path = os.path.abspath(os.path.join(FILE, '../../../utils/pb/fraud_detection')) +fraud_detection_grpc_path = os.path.abspath( + os.path.join(FILE, '../../../utils/pb/fraud_detection') +) sys.path.insert(0, fraud_detection_grpc_path) + +import grpc import fraud_detection_pb2 as fraud_detection import fraud_detection_pb2_grpc as fraud_detection_grpc -import grpc -from concurrent import futures -# Create a class to define the server functions, derived from -# fraud_detection_pb2_grpc.HelloServiceServicer class HelloService(fraud_detection_grpc.HelloServiceServicer): - # Create an RPC function to say hello def SayHello(self, request, context): - # Create a HelloResponse object response = fraud_detection.HelloResponse() - # Set the greeting field of the response object response.greeting = "Hello, " + request.name - # Print the greeting message print(response.greeting) - # Return the response object return response + def CheckFraud(self, request, context): + print("Received fraud check request") + print("user_name:", request.user_name) + masked_card_number = mask_fixed(request.card_number) + print("card_number:", masked_card_number) + print("item_count:", request.item_count) + + # Compute length based on digits only to avoid counting spaces or other characters + card_digits = extract_card_digits(request.card_number) + print("card length (digits only):", len(card_digits)) + is_fraud = False + message = "No fraud detected." + + # Very simple dummy rules for now + if request.item_count > 20: + is_fraud = True + message = "Too many items in order." + + # Treat any non-16-digit card number as invalid + elif len(card_digits) != 16: + is_fraud = True + message = "Invalid card number." + + elif card_digits.startswith("0000"): + is_fraud = True + message = "Suspicious card number pattern." + + elif card_digits.endswith("0000"): + is_fraud = True + message = "Suspicious card number pattern." + elif "fraud" in request.user_name.lower(): + is_fraud = True + message = "Suspicious user name." + + response = fraud_detection.FraudCheckResponse() + response.is_fraud = is_fraud + response.message = message + + print("Returning fraud result:", response.is_fraud, response.message) + return response + + +def extract_card_digits(card: str) -> str: + """ + Return only the digit characters from the given card number. + """ + return ''.join(c for c in str(card) if c.isdigit()) + + def serve(): - # Create a gRPC server server = grpc.server(futures.ThreadPoolExecutor()) - # Add HelloService fraud_detection_grpc.add_HelloServiceServicer_to_server(HelloService(), server) - # Listen on port 50051 + port = "50051" server.add_insecure_port("[::]:" + port) - # Start the server server.start() - print("Server started. Listening on port 50051.") - # Keep thread alive + print("Fraud detection server started. Listening on port 50051.") server.wait_for_termination() +def mask_fixed(card: str) -> str: + digits = ''.join(c for c in str(card) if c.isdigit()) + masked = '*' * 12 + digits[-4:].rjust(4, '*') + return ' '.join(masked[i:i+4] for i in range(0, 16, 4)) + if __name__ == '__main__': serve() \ No newline at end of file diff --git a/frontend/src/index.html b/frontend/src/index.html index 15c47351f..2ace91b7d 100644 --- a/frontend/src/index.html +++ b/frontend/src/index.html @@ -22,26 +22,32 @@

Items

+
+
+
+
+
+
@@ -50,6 +56,7 @@

Items

+
+
+
- + + @@ -90,17 +102,18 @@

Items

event.preventDefault(); const formData = new FormData(event.target); + const data = { user: { name: formData.get('name'), contact: formData.get('contact'), + creditCard: { + number: formData.get('creditCard'), + expirationDate: formData.get('expirationDate'), + cvv: formData.get('cvv'), + }, + userComment: formData.get('userComment'), }, - creditCard: { - number: formData.get('creditCard'), - expirationDate: formData.get('expirationDate'), - cvv: formData.get('cvv'), - }, - userComment: formData.get('userComment'), items: items, billingAddress: { street: formData.get('billingStreet'), @@ -111,12 +124,15 @@

Items

}, shippingMethod: formData.get('shippingMethod'), giftWrapping: formData.get('giftWrapping') === 'on', - termsAccepted: formData.get('terms') === 'on', + termsAndConditionsAccepted: formData.get('terms') === 'on', }; try { const response = await fetch('http://localhost:8081/checkout', { method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, body: JSON.stringify(data), }); @@ -124,25 +140,29 @@

Items

const responseDiv = document.getElementById('response'); if (response.ok) { - const suggestedBooks = result.suggestedBooks.map(book => `
  • ${book.title} by ${book.author}
  • `).join(''); + const suggestedBooks = (result.suggestedBooks || []) + .map(book => `
  • ${book.title} by ${book.author}
  • `) + .join(''); + responseDiv.innerHTML = ` Order status: ${result.status}
    Order ID: ${result.orderId}
    - ${result.status === "Order Approved" ? - `Suggested Books: - ` - : `` + ${result.status === "Order Approved" + ? `Suggested Books: + ` + : `` } `; - color = result.status === 'Order Approved' ? 'green' : 'red'; + + const color = result.status === 'Order Approved' ? 'green' : 'red'; responseDiv.className = `mt-6 p-4 border rounded-lg bg-${color}-100 text-${color}-700`; } else { - responseDiv.textContent = `Error: ${result.error.message}`; + const message = result?.error?.message || 'Unknown error'; + responseDiv.textContent = `Error: ${message}`; responseDiv.className = "mt-6 p-4 border rounded-lg bg-red-100 text-red-700"; } responseDiv.style.display = 'block'; - // scroll to the bottom of the page window.scrollTo(0, document.body.scrollHeight); } catch (error) { const responseDiv = document.getElementById('response'); @@ -153,4 +173,4 @@

    Items

    }); - + \ No newline at end of file diff --git a/orchestrator/src/app.py b/orchestrator/src/app.py index 62d5d0662..590815ff5 100644 --- a/orchestrator/src/app.py +++ b/orchestrator/src/app.py @@ -1,75 +1,269 @@ import sys import os +import threading # This set of lines are needed to import the gRPC stubs. # The path of the stubs is relative to the current file, or absolute inside the container. # Change these lines only if strictly needed. FILE = __file__ if '__file__' in globals() else os.getenv("PYTHONFILE", "") -fraud_detection_grpc_path = os.path.abspath(os.path.join(FILE, '../../../utils/pb/fraud_detection')) + +fraud_detection_grpc_path = os.path.abspath( + os.path.join(FILE, '../../../utils/pb/fraud_detection') +) sys.path.insert(0, fraud_detection_grpc_path) import fraud_detection_pb2 as fraud_detection import fraud_detection_pb2_grpc as fraud_detection_grpc +transaction_verification_grpc_path = os.path.abspath( + os.path.join(FILE, '../../../utils/pb/transaction_verification') +) +sys.path.insert(0, transaction_verification_grpc_path) +import transaction_verification_pb2 as transaction_verification +import transaction_verification_pb2_grpc as transaction_verification_grpc + +suggestions_grpc_path = os.path.abspath( + os.path.join(FILE, '../../../utils/pb/suggestions') +) +sys.path.insert(0, suggestions_grpc_path) +import suggestions_pb2 as suggestions +import suggestions_pb2_grpc as suggestions_grpc + import grpc +from flask import Flask, request +from flask_cors import CORS + + def greet(name='you'): - # Establish a connection with the fraud-detection gRPC service. with grpc.insecure_channel('fraud_detection:50051') as channel: - # Create a stub object. stub = fraud_detection_grpc.HelloServiceStub(channel) - # Call the service through the stub object. response = stub.SayHello(fraud_detection.HelloRequest(name=name)) return response.greeting -# Import Flask. -# Flask is a web framework for Python. -# It allows you to build a web application quickly. -# For more information, see https://flask.palletsprojects.com/en/latest/ -from flask import Flask, request -from flask_cors import CORS -import json -# Create a simple Flask app. +def detect_fraud(user_name, card_number, item_count): + with grpc.insecure_channel('fraud_detection:50051') as channel: + stub = fraud_detection_grpc.HelloServiceStub(channel) + response = stub.CheckFraud( + fraud_detection.FraudCheckRequest( + user_name=user_name or "", + card_number=card_number or "", + item_count=item_count + ), + timeout=5.0 # seconds + ) + return response + +def verify_transaction(user_name, user_contact, card_number, expiration_date, cvv, item_count, terms_accepted): + with grpc.insecure_channel('transaction_verification:50052') as channel: + stub = transaction_verification_grpc.TransactionVerificationServiceStub(channel) + response = stub.VerifyTransaction( + transaction_verification.TransactionVerificationRequest( + user_name=user_name or "", + user_contact=user_contact or "", + card_number=card_number or "", + expiration_date=expiration_date or "", + cvv=cvv or "", + item_count=item_count, + terms_accepted=terms_accepted + ) + ) + return response + + +def get_suggestions(user_name, item_count): + with grpc.insecure_channel('suggestions:50053') as channel: + stub = suggestions_grpc.SuggestionsServiceStub(channel) + response = stub.GetSuggestions( + suggestions.SuggestionsRequest( + user_name=user_name or "", + item_count=item_count + ) + ) + return response + + app = Flask(__name__) -# Enable CORS for the app. CORS(app, resources={r'/*': {'origins': '*'}}) -# Define a GET endpoint. + @app.route('/', methods=['GET']) def index(): - """ - Responds with 'Hello, [name]' when a GET request is made to '/' endpoint. - """ - # Test the fraud-detection gRPC service. response = greet(name='orchestrator') - # Return the response. return response + @app.route('/checkout', methods=['POST']) def checkout(): - """ - Responds with a JSON object containing the order ID, status, and suggested books. - """ - # Get request object data to json - request_data = json.loads(request.data) - # Print request object data - print("Request Data:", request_data.get('items')) - - # Dummy response following the provided YAML specification for the bookstore - order_status_response = { - 'orderId': '12345', - 'status': 'Order Approved', - 'suggestedBooks': [ - {'bookId': '123', 'title': 'The Best Book', 'author': 'Author 1'}, - {'bookId': '456', 'title': 'The Second Best Book', 'author': 'Author 2'} - ] + request_data = request.get_json(silent=True) + + if request_data is None: + return { + "error": { + "code": "BAD_REQUEST", + "message": "Request body must be valid JSON." + } + }, 400 + + user = request_data.get("user", {}) + items = request_data.get("items", []) + shipping_method = request_data.get("shippingMethod") + terms_accepted = request_data.get("termsAndConditionsAccepted", False) + + # Normalize user fields and enforce that user_name is not empty or just spaces + user_name = (user.get("name") or "").strip() + user_contact = (user.get("contact") or "").strip() + user_comment = user.get("userComment", "") + + credit_card = user.get("creditCard", {}) + card_number = credit_card.get("number").strip() + masked_card_number = mask_fixed(card_number) + expiration_date = credit_card.get("expirationDate").strip() + cvv = credit_card.get("cvv").strip() + print( + "Received a request for checkout of user : {} for card number : {}".format( + user_name, masked_card_number + ) + ) + + + # Keep these simple bad-request checks locally + # user_name must not be empty or contain spaces + if not user_name or "" == user_name: + return { + "error": { + "code": "BAD_REQUEST", + "message": "User name is required and must not contain spaces." + } + }, 400 + + if not user_contact or "" == user_contact: + return { + "error": { + "code": "BAD_REQUEST", + "message": "User contact is required and must not contain spaces." + } + }, 400 + + item_count = len(items) + + results = { + "fraud": None, + "verification": None, + "suggestions": None, + "errors": [] } - return order_status_response + def fraud_worker(): + try: + print("Calling fraud_detection service...") + results["fraud"] = detect_fraud( + user_name=user_name, + card_number=card_number, + item_count=item_count + ) + print( + "fraud_detection result:", + results["fraud"].is_fraud, + results["fraud"].message + ) + except Exception as e: + print(f"fraud_detection failed: {e}") # Full detail for server logs + results["errors"].append("fraud_detection service unavailable") # Sanitized for client + def verification_worker(): + try: + print("Calling transaction_verification service...") + results["verification"] = verify_transaction( + user_name=user_name, + user_contact=user_contact, + card_number=card_number, + expiration_date=expiration_date, + cvv=cvv, + item_count=item_count, + terms_accepted=terms_accepted + ) + print( + "transaction_verification result:", + results["verification"].is_valid, + results["verification"].message + ) + except Exception as e: + error_msg = f"transaction_verification failed: {e}" + print(error_msg) + results["errors"].append(error_msg) + + def suggestions_worker(): + try: + print("Calling suggestions service...") + results["suggestions"] = get_suggestions( + user_name=user_name, + item_count=item_count + ) + print( + "suggestions returned:", + len(results["suggestions"].books), + "books" + ) + except Exception as e: + error_msg = f"suggestions failed: {e}" + print(error_msg) + results["errors"].append(error_msg) + + fraud_thread = threading.Thread(target=fraud_worker) + verification_thread = threading.Thread(target=verification_worker) + suggestions_thread = threading.Thread(target=suggestions_worker) + + print("Starting worker threads...") + fraud_thread.start() + verification_thread.start() + suggestions_thread.start() + + fraud_thread.join() + verification_thread.join() + suggestions_thread.join() + print("All worker threads finished.") + + if results["errors"]: + return { + "error": { + "code": "INTERNAL_ERROR", + "message": "; ".join(results["errors"]) + } + }, 500 + + if results["fraud"] and results["fraud"].is_fraud: + return { + "orderId": "12345", + "status": "Order Rejected", + "suggestedBooks": [] + }, 200 + + if results["verification"] and not results["verification"].is_valid: + return { + "orderId": "12345", + "status": "Order Rejected", + "suggestedBooks": [] + }, 200 + + suggested_books = [] + if results["suggestions"]: + for book in results["suggestions"].books: + suggested_books.append({ + "bookId": book.bookId, + "title": book.title, + "author": book.author + }) + + return { + "orderId": "12345", + "status": "Order Approved", + "suggestedBooks": suggested_books + }, 200 +def mask_fixed(card: str) -> str: + digits = ''.join(c for c in str(card) if c.isdigit()) + masked = '*' * 12 + digits[-4:].rjust(4, '*') + return ' '.join(masked[i:i+4] for i in range(0, 16, 4)) if __name__ == '__main__': - # Run the app in debug mode to enable hot reloading. - # This is useful for development. - # The default port is 5000. - app.run(host='0.0.0.0') + app.run(host='0.0.0.0') \ No newline at end of file diff --git a/suggestions/Dockerfile b/suggestions/Dockerfile new file mode 100644 index 000000000..1c35664df --- /dev/null +++ b/suggestions/Dockerfile @@ -0,0 +1,8 @@ +FROM python:3.11 + +WORKDIR /app + +COPY ./suggestions/requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +CMD python utils/other/hotreload.py "suggestions/src/app.py" \ No newline at end of file diff --git a/suggestions/requirements.txt b/suggestions/requirements.txt new file mode 100644 index 000000000..43fdaf26f --- /dev/null +++ b/suggestions/requirements.txt @@ -0,0 +1,4 @@ +grpcio==1.60.0 +grpcio-tools==1.60.0 +protobuf==4.25.2 +watchdog==6.0.0 \ No newline at end of file diff --git a/suggestions/src/app.py b/suggestions/src/app.py new file mode 100644 index 000000000..258c2fb0c --- /dev/null +++ b/suggestions/src/app.py @@ -0,0 +1,60 @@ +import sys +import os +from concurrent import futures + +FILE = __file__ if '__file__' in globals() else os.getenv("PYTHONFILE", "") +suggestions_grpc_path = os.path.abspath( + os.path.join(FILE, '../../../utils/pb/suggestions') +) +sys.path.insert(0, suggestions_grpc_path) + +import grpc +import suggestions_pb2 as suggestions +import suggestions_pb2_grpc as suggestions_grpc + + +class SuggestionsService(suggestions_grpc.SuggestionsServiceServicer): + def GetSuggestions(self, request, context): + print("Received suggestions request") + print("user_name:", request.user_name) + print("item_count:", request.item_count) + + static_books = [ + {"bookId": "101", "title": "Distributed Systems Basics", "author": "A. Author"}, + {"bookId": "102", "title": "Designing Data-Intensive Applications", "author": "Martin Kleppmann"}, + {"bookId": "103", "title": "Clean Code", "author": "Robert C. Martin"}, + {"bookId": "104", "title": "The Pragmatic Programmer", "author": "Andrew Hunt"}, + ] + + response = suggestions.SuggestionsResponse() + + if request.item_count > 0: + chosen = static_books[:2] + else: + chosen = [] + + for book in chosen: + b = response.books.add() + b.bookId = book["bookId"] + b.title = book["title"] + b.author = book["author"] + + print("Returning", len(response.books), "suggested books") + return response + + +def serve(): + server = grpc.server(futures.ThreadPoolExecutor()) + suggestions_grpc.add_SuggestionsServiceServicer_to_server( + SuggestionsService(), server + ) + + port = "50053" + server.add_insecure_port("[::]:" + port) + server.start() + print("Suggestions server started. Listening on port 50053.") + server.wait_for_termination() + + +if __name__ == '__main__': + serve() \ No newline at end of file diff --git a/system_diagram.png b/system_diagram.png new file mode 100644 index 000000000..e7ba1b6c5 Binary files /dev/null and b/system_diagram.png differ diff --git a/test_checkout.json b/test_checkout.json new file mode 100644 index 000000000..a78ba6f7a --- /dev/null +++ b/test_checkout.json @@ -0,0 +1,20 @@ +{ + "user": { + "name": "Test User", + "contact": "test@example.com", + "creditCard": { + "number": "4111111111111111", + "expirationDate": "12/30", + "cvv": "123" + }, + "userComment": "Please handle with care." + }, + "items": [ + { + "name": "Book A", + "quantity": 1 + } + ], + "shippingMethod": "Standard", + "termsAndConditionsAccepted": true +} diff --git a/test_checkout_empty_items.json b/test_checkout_empty_items.json new file mode 100644 index 000000000..df39e004d --- /dev/null +++ b/test_checkout_empty_items.json @@ -0,0 +1,15 @@ +{ + "user": { + "name": "Test User", + "contact": "test@example.com", + "creditCard": { + "number": "4111111111111111", + "expirationDate": "12/30", + "cvv": "123" + }, + "userComment": "Please handle with care." + }, + "items": [], + "shippingMethod": "Standard", + "termsAndConditionsAccepted": true +} diff --git a/test_checkout_fraud.json b/test_checkout_fraud.json new file mode 100644 index 000000000..8c7677348 --- /dev/null +++ b/test_checkout_fraud.json @@ -0,0 +1,20 @@ +{ + "user": { + "name": "Test User", + "contact": "test@example.com", + "creditCard": { + "number": "4111111111110000", + "expirationDate": "12/30", + "cvv": "123" + }, + "userComment": "Please handle with care." + }, + "items": [ + { + "name": "Book A", + "quantity": 1 + } + ], + "shippingMethod": "Standard", + "termsAndConditionsAccepted": true +} diff --git a/test_checkout_terms_false.json b/test_checkout_terms_false.json new file mode 100644 index 000000000..82087d4b1 --- /dev/null +++ b/test_checkout_terms_false.json @@ -0,0 +1,20 @@ +{ + "user": { + "name": "Test User", + "contact": "test@example.com", + "creditCard": { + "number": "4111111111111111", + "expirationDate": "12/30", + "cvv": "123" + }, + "userComment": "Please handle with care." + }, + "items": [ + { + "name": "Book A", + "quantity": 1 + } + ], + "shippingMethod": "Standard", + "termsAndConditionsAccepted": false +} diff --git a/transaction_verification/Dockerfile b/transaction_verification/Dockerfile new file mode 100644 index 000000000..ad4936c4a --- /dev/null +++ b/transaction_verification/Dockerfile @@ -0,0 +1,8 @@ +FROM python:3.11 + +WORKDIR /app + +COPY ./transaction_verification/requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +CMD python utils/other/hotreload.py "transaction_verification/src/app.py" \ No newline at end of file diff --git a/transaction_verification/requirements.txt b/transaction_verification/requirements.txt new file mode 100644 index 000000000..43fdaf26f --- /dev/null +++ b/transaction_verification/requirements.txt @@ -0,0 +1,4 @@ +grpcio==1.60.0 +grpcio-tools==1.60.0 +protobuf==4.25.2 +watchdog==6.0.0 \ No newline at end of file diff --git a/transaction_verification/src/app.py b/transaction_verification/src/app.py new file mode 100644 index 000000000..b1087e51f --- /dev/null +++ b/transaction_verification/src/app.py @@ -0,0 +1,87 @@ +import sys +import os +from concurrent import futures + +FILE = __file__ if '__file__' in globals() else os.getenv("PYTHONFILE", "") +transaction_verification_grpc_path = os.path.abspath( + os.path.join(FILE, '../../../utils/pb/transaction_verification') +) +sys.path.insert(0, transaction_verification_grpc_path) + +import grpc +import transaction_verification_pb2 as transaction_verification +import transaction_verification_pb2_grpc as transaction_verification_grpc + + +class TransactionVerificationService( + transaction_verification_grpc.TransactionVerificationServiceServicer +): + def VerifyTransaction(self, request, context): + print("Received transaction verification request") + print("user_name:", request.user_name) + print("user_contact:", request.user_contact) + masked_card_number = mask_fixed(request.card_number) + print("card_number:", masked_card_number) + print("item_count:", request.item_count) + print("terms_accepted:", request.terms_accepted) + # Compute length based on digits only to avoid counting spaces or other characters + card_digits = extract_card_digits(request.card_number) + print("card length (digits only):", len(card_digits)) + is_valid = True + message = "Transaction is valid." + + if not request.user_name: + is_valid = False + message = "Missing user name." + elif not request.user_contact: + is_valid = False + message = "Missing user contact." + elif request.item_count <= 0: + is_valid = False + message = "No items in order." + elif not request.terms_accepted: + is_valid = False + message = "Terms and conditions not accepted." + elif not request.card_number or not request.expiration_date or not request.cvv: + is_valid = False + message = "Missing credit card information." + + # Treat any non-16-digit card number as invalid + elif len(card_digits) != 16: + is_valid = False + message = "Invalid card number." + + response = transaction_verification.TransactionVerificationResponse() + response.is_valid = is_valid + response.message = message + + print("Returning verification result:", response.is_valid, response.message) + return response + + +def extract_card_digits(card: str) -> str: + """ + Return only the digit characters from the given card number. + """ + return ''.join(c for c in str(card) if c.isdigit()) + + +def serve(): + server = grpc.server(futures.ThreadPoolExecutor()) + transaction_verification_grpc.add_TransactionVerificationServiceServicer_to_server( + TransactionVerificationService(), server + ) + + port = "50052" + server.add_insecure_port("[::]:" + port) + server.start() + print("Transaction verification server started. Listening on port 50052.") + server.wait_for_termination() + +def mask_fixed(card: str) -> str: + digits = ''.join(c for c in str(card) if c.isdigit()) + masked = '*' * 12 + digits[-4:].rjust(4, '*') + return ' '.join(masked[i:i+4] for i in range(0, 16, 4)) + +if __name__ == '__main__': + serve() \ No newline at end of file diff --git a/utils/pb/fraud_detection/fraud_detection.proto b/utils/pb/fraud_detection/fraud_detection.proto index db20211d7..d575366b1 100644 --- a/utils/pb/fraud_detection/fraud_detection.proto +++ b/utils/pb/fraud_detection/fraud_detection.proto @@ -3,13 +3,25 @@ syntax = "proto3"; package hello; service HelloService { - rpc SayHello (HelloRequest) returns (HelloResponse); + rpc SayHello (HelloRequest) returns (HelloResponse); + rpc CheckFraud (FraudCheckRequest) returns (FraudCheckResponse); } message HelloRequest { - string name = 1; + string name = 1; } message HelloResponse { - string greeting = 1; + string greeting = 1; } + +message FraudCheckRequest { + string user_name = 1; + string card_number = 2; + int32 item_count = 3; +} + +message FraudCheckResponse { + bool is_fraud = 1; + string message = 2; +} \ No newline at end of file diff --git a/utils/pb/fraud_detection/fraud_detection_pb2.py b/utils/pb/fraud_detection/fraud_detection_pb2.py index cdd0bcae8..d405d9cf5 100644 --- a/utils/pb/fraud_detection/fraud_detection_pb2.py +++ b/utils/pb/fraud_detection/fraud_detection_pb2.py @@ -14,7 +14,7 @@ -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x15\x66raud_detection.proto\x12\x05hello\"\x1c\n\x0cHelloRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\"!\n\rHelloResponse\x12\x10\n\x08greeting\x18\x01 \x01(\t2E\n\x0cHelloService\x12\x35\n\x08SayHello\x12\x13.hello.HelloRequest\x1a\x14.hello.HelloResponseb\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x15\x66raud_detection.proto\x12\x05hello\"\x1c\n\x0cHelloRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\"!\n\rHelloResponse\x12\x10\n\x08greeting\x18\x01 \x01(\t\"O\n\x11\x46raudCheckRequest\x12\x11\n\tuser_name\x18\x01 \x01(\t\x12\x13\n\x0b\x63\x61rd_number\x18\x02 \x01(\t\x12\x12\n\nitem_count\x18\x03 \x01(\x05\"7\n\x12\x46raudCheckResponse\x12\x10\n\x08is_fraud\x18\x01 \x01(\x08\x12\x0f\n\x07message\x18\x02 \x01(\t2\x88\x01\n\x0cHelloService\x12\x35\n\x08SayHello\x12\x13.hello.HelloRequest\x1a\x14.hello.HelloResponse\x12\x41\n\nCheckFraud\x12\x18.hello.FraudCheckRequest\x1a\x19.hello.FraudCheckResponseb\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) @@ -25,6 +25,10 @@ _globals['_HELLOREQUEST']._serialized_end=60 _globals['_HELLORESPONSE']._serialized_start=62 _globals['_HELLORESPONSE']._serialized_end=95 - _globals['_HELLOSERVICE']._serialized_start=97 - _globals['_HELLOSERVICE']._serialized_end=166 + _globals['_FRAUDCHECKREQUEST']._serialized_start=97 + _globals['_FRAUDCHECKREQUEST']._serialized_end=176 + _globals['_FRAUDCHECKRESPONSE']._serialized_start=178 + _globals['_FRAUDCHECKRESPONSE']._serialized_end=233 + _globals['_HELLOSERVICE']._serialized_start=236 + _globals['_HELLOSERVICE']._serialized_end=372 # @@protoc_insertion_point(module_scope) diff --git a/utils/pb/fraud_detection/fraud_detection_pb2.pyi b/utils/pb/fraud_detection/fraud_detection_pb2.pyi index 30a263856..fe3863b06 100644 --- a/utils/pb/fraud_detection/fraud_detection_pb2.pyi +++ b/utils/pb/fraud_detection/fraud_detection_pb2.pyi @@ -15,3 +15,21 @@ class HelloResponse(_message.Message): GREETING_FIELD_NUMBER: _ClassVar[int] greeting: str def __init__(self, greeting: _Optional[str] = ...) -> None: ... + +class FraudCheckRequest(_message.Message): + __slots__ = ("user_name", "card_number", "item_count") + USER_NAME_FIELD_NUMBER: _ClassVar[int] + CARD_NUMBER_FIELD_NUMBER: _ClassVar[int] + ITEM_COUNT_FIELD_NUMBER: _ClassVar[int] + user_name: str + card_number: str + item_count: int + def __init__(self, user_name: _Optional[str] = ..., card_number: _Optional[str] = ..., item_count: _Optional[int] = ...) -> None: ... + +class FraudCheckResponse(_message.Message): + __slots__ = ("is_fraud", "message") + IS_FRAUD_FIELD_NUMBER: _ClassVar[int] + MESSAGE_FIELD_NUMBER: _ClassVar[int] + is_fraud: bool + message: str + def __init__(self, is_fraud: bool = ..., message: _Optional[str] = ...) -> None: ... diff --git a/utils/pb/fraud_detection/fraud_detection_pb2_grpc.py b/utils/pb/fraud_detection/fraud_detection_pb2_grpc.py index 4e7a27975..3ada2e86e 100644 --- a/utils/pb/fraud_detection/fraud_detection_pb2_grpc.py +++ b/utils/pb/fraud_detection/fraud_detection_pb2_grpc.py @@ -19,6 +19,11 @@ def __init__(self, channel): request_serializer=fraud__detection__pb2.HelloRequest.SerializeToString, response_deserializer=fraud__detection__pb2.HelloResponse.FromString, ) + self.CheckFraud = channel.unary_unary( + '/hello.HelloService/CheckFraud', + request_serializer=fraud__detection__pb2.FraudCheckRequest.SerializeToString, + response_deserializer=fraud__detection__pb2.FraudCheckResponse.FromString, + ) class HelloServiceServicer(object): @@ -30,6 +35,12 @@ def SayHello(self, request, context): context.set_details('Method not implemented!') raise NotImplementedError('Method not implemented!') + def CheckFraud(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + def add_HelloServiceServicer_to_server(servicer, server): rpc_method_handlers = { @@ -38,6 +49,11 @@ def add_HelloServiceServicer_to_server(servicer, server): request_deserializer=fraud__detection__pb2.HelloRequest.FromString, response_serializer=fraud__detection__pb2.HelloResponse.SerializeToString, ), + 'CheckFraud': grpc.unary_unary_rpc_method_handler( + servicer.CheckFraud, + request_deserializer=fraud__detection__pb2.FraudCheckRequest.FromString, + response_serializer=fraud__detection__pb2.FraudCheckResponse.SerializeToString, + ), } generic_handler = grpc.method_handlers_generic_handler( 'hello.HelloService', rpc_method_handlers) @@ -64,3 +80,20 @@ def SayHello(request, fraud__detection__pb2.HelloResponse.FromString, options, channel_credentials, insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def CheckFraud(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/hello.HelloService/CheckFraud', + fraud__detection__pb2.FraudCheckRequest.SerializeToString, + fraud__detection__pb2.FraudCheckResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) diff --git a/utils/pb/suggestions/__init__.py b/utils/pb/suggestions/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/utils/pb/suggestions/suggestions.proto b/utils/pb/suggestions/suggestions.proto new file mode 100644 index 000000000..12f15f4cb --- /dev/null +++ b/utils/pb/suggestions/suggestions.proto @@ -0,0 +1,22 @@ +syntax = "proto3"; + +package suggestions; + +service SuggestionsService { + rpc GetSuggestions (SuggestionsRequest) returns (SuggestionsResponse); +} + +message SuggestionsRequest { + string user_name = 1; + int32 item_count = 2; +} + +message SuggestedBook { + string bookId = 1; + string title = 2; + string author = 3; +} + +message SuggestionsResponse { + repeated SuggestedBook books = 1; +} \ No newline at end of file diff --git a/utils/pb/suggestions/suggestions_pb2.py b/utils/pb/suggestions/suggestions_pb2.py new file mode 100644 index 000000000..550a4d48e --- /dev/null +++ b/utils/pb/suggestions/suggestions_pb2.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: suggestions.proto +# Protobuf Python Version: 4.25.0 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x11suggestions.proto\x12\x0bsuggestions\";\n\x12SuggestionsRequest\x12\x11\n\tuser_name\x18\x01 \x01(\t\x12\x12\n\nitem_count\x18\x02 \x01(\x05\">\n\rSuggestedBook\x12\x0e\n\x06\x62ookId\x18\x01 \x01(\t\x12\r\n\x05title\x18\x02 \x01(\t\x12\x0e\n\x06\x61uthor\x18\x03 \x01(\t\"@\n\x13SuggestionsResponse\x12)\n\x05\x62ooks\x18\x01 \x03(\x0b\x32\x1a.suggestions.SuggestedBook2i\n\x12SuggestionsService\x12S\n\x0eGetSuggestions\x12\x1f.suggestions.SuggestionsRequest\x1a .suggestions.SuggestionsResponseb\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'suggestions_pb2', _globals) +if _descriptor._USE_C_DESCRIPTORS == False: + DESCRIPTOR._options = None + _globals['_SUGGESTIONSREQUEST']._serialized_start=34 + _globals['_SUGGESTIONSREQUEST']._serialized_end=93 + _globals['_SUGGESTEDBOOK']._serialized_start=95 + _globals['_SUGGESTEDBOOK']._serialized_end=157 + _globals['_SUGGESTIONSRESPONSE']._serialized_start=159 + _globals['_SUGGESTIONSRESPONSE']._serialized_end=223 + _globals['_SUGGESTIONSSERVICE']._serialized_start=225 + _globals['_SUGGESTIONSSERVICE']._serialized_end=330 +# @@protoc_insertion_point(module_scope) diff --git a/utils/pb/suggestions/suggestions_pb2.pyi b/utils/pb/suggestions/suggestions_pb2.pyi new file mode 100644 index 000000000..d3d31c343 --- /dev/null +++ b/utils/pb/suggestions/suggestions_pb2.pyi @@ -0,0 +1,30 @@ +from google.protobuf.internal import containers as _containers +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from typing import ClassVar as _ClassVar, Iterable as _Iterable, Mapping as _Mapping, Optional as _Optional, Union as _Union + +DESCRIPTOR: _descriptor.FileDescriptor + +class SuggestionsRequest(_message.Message): + __slots__ = ("user_name", "item_count") + USER_NAME_FIELD_NUMBER: _ClassVar[int] + ITEM_COUNT_FIELD_NUMBER: _ClassVar[int] + user_name: str + item_count: int + def __init__(self, user_name: _Optional[str] = ..., item_count: _Optional[int] = ...) -> None: ... + +class SuggestedBook(_message.Message): + __slots__ = ("bookId", "title", "author") + BOOKID_FIELD_NUMBER: _ClassVar[int] + TITLE_FIELD_NUMBER: _ClassVar[int] + AUTHOR_FIELD_NUMBER: _ClassVar[int] + bookId: str + title: str + author: str + def __init__(self, bookId: _Optional[str] = ..., title: _Optional[str] = ..., author: _Optional[str] = ...) -> None: ... + +class SuggestionsResponse(_message.Message): + __slots__ = ("books",) + BOOKS_FIELD_NUMBER: _ClassVar[int] + books: _containers.RepeatedCompositeFieldContainer[SuggestedBook] + def __init__(self, books: _Optional[_Iterable[_Union[SuggestedBook, _Mapping]]] = ...) -> None: ... diff --git a/utils/pb/suggestions/suggestions_pb2_grpc.py b/utils/pb/suggestions/suggestions_pb2_grpc.py new file mode 100644 index 000000000..ca1154a0b --- /dev/null +++ b/utils/pb/suggestions/suggestions_pb2_grpc.py @@ -0,0 +1,66 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc + +import suggestions_pb2 as suggestions__pb2 + + +class SuggestionsServiceStub(object): + """Missing associated documentation comment in .proto file.""" + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.GetSuggestions = channel.unary_unary( + '/suggestions.SuggestionsService/GetSuggestions', + request_serializer=suggestions__pb2.SuggestionsRequest.SerializeToString, + response_deserializer=suggestions__pb2.SuggestionsResponse.FromString, + ) + + +class SuggestionsServiceServicer(object): + """Missing associated documentation comment in .proto file.""" + + def GetSuggestions(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_SuggestionsServiceServicer_to_server(servicer, server): + rpc_method_handlers = { + 'GetSuggestions': grpc.unary_unary_rpc_method_handler( + servicer.GetSuggestions, + request_deserializer=suggestions__pb2.SuggestionsRequest.FromString, + response_serializer=suggestions__pb2.SuggestionsResponse.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'suggestions.SuggestionsService', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + + + # This class is part of an EXPERIMENTAL API. +class SuggestionsService(object): + """Missing associated documentation comment in .proto file.""" + + @staticmethod + def GetSuggestions(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/suggestions.SuggestionsService/GetSuggestions', + suggestions__pb2.SuggestionsRequest.SerializeToString, + suggestions__pb2.SuggestionsResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) diff --git a/utils/pb/transaction_verification/__init__.py b/utils/pb/transaction_verification/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/utils/pb/transaction_verification/transaction_verification.proto b/utils/pb/transaction_verification/transaction_verification.proto new file mode 100644 index 000000000..44ba9cec7 --- /dev/null +++ b/utils/pb/transaction_verification/transaction_verification.proto @@ -0,0 +1,22 @@ +syntax = "proto3"; + +package transaction_verification; + +service TransactionVerificationService { + rpc VerifyTransaction (TransactionVerificationRequest) returns (TransactionVerificationResponse); +} + +message TransactionVerificationRequest { + string user_name = 1; + string user_contact = 2; + string card_number = 3; + string expiration_date = 4; + string cvv = 5; + int32 item_count = 6; + bool terms_accepted = 7; +} + +message TransactionVerificationResponse { + bool is_valid = 1; + string message = 2; +} \ No newline at end of file diff --git a/utils/pb/transaction_verification/transaction_verification_pb2.py b/utils/pb/transaction_verification/transaction_verification_pb2.py new file mode 100644 index 000000000..447731158 --- /dev/null +++ b/utils/pb/transaction_verification/transaction_verification_pb2.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: transaction_verification.proto +# Protobuf Python Version: 4.25.0 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1etransaction_verification.proto\x12\x18transaction_verification\"\xb0\x01\n\x1eTransactionVerificationRequest\x12\x11\n\tuser_name\x18\x01 \x01(\t\x12\x14\n\x0cuser_contact\x18\x02 \x01(\t\x12\x13\n\x0b\x63\x61rd_number\x18\x03 \x01(\t\x12\x17\n\x0f\x65xpiration_date\x18\x04 \x01(\t\x12\x0b\n\x03\x63vv\x18\x05 \x01(\t\x12\x12\n\nitem_count\x18\x06 \x01(\x05\x12\x16\n\x0eterms_accepted\x18\x07 \x01(\x08\"D\n\x1fTransactionVerificationResponse\x12\x10\n\x08is_valid\x18\x01 \x01(\x08\x12\x0f\n\x07message\x18\x02 \x01(\t2\xab\x01\n\x1eTransactionVerificationService\x12\x88\x01\n\x11VerifyTransaction\x12\x38.transaction_verification.TransactionVerificationRequest\x1a\x39.transaction_verification.TransactionVerificationResponseb\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'transaction_verification_pb2', _globals) +if _descriptor._USE_C_DESCRIPTORS == False: + DESCRIPTOR._options = None + _globals['_TRANSACTIONVERIFICATIONREQUEST']._serialized_start=61 + _globals['_TRANSACTIONVERIFICATIONREQUEST']._serialized_end=237 + _globals['_TRANSACTIONVERIFICATIONRESPONSE']._serialized_start=239 + _globals['_TRANSACTIONVERIFICATIONRESPONSE']._serialized_end=307 + _globals['_TRANSACTIONVERIFICATIONSERVICE']._serialized_start=310 + _globals['_TRANSACTIONVERIFICATIONSERVICE']._serialized_end=481 +# @@protoc_insertion_point(module_scope) diff --git a/utils/pb/transaction_verification/transaction_verification_pb2.pyi b/utils/pb/transaction_verification/transaction_verification_pb2.pyi new file mode 100644 index 000000000..5bb457074 --- /dev/null +++ b/utils/pb/transaction_verification/transaction_verification_pb2.pyi @@ -0,0 +1,31 @@ +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from typing import ClassVar as _ClassVar, Optional as _Optional + +DESCRIPTOR: _descriptor.FileDescriptor + +class TransactionVerificationRequest(_message.Message): + __slots__ = ("user_name", "user_contact", "card_number", "expiration_date", "cvv", "item_count", "terms_accepted") + USER_NAME_FIELD_NUMBER: _ClassVar[int] + USER_CONTACT_FIELD_NUMBER: _ClassVar[int] + CARD_NUMBER_FIELD_NUMBER: _ClassVar[int] + EXPIRATION_DATE_FIELD_NUMBER: _ClassVar[int] + CVV_FIELD_NUMBER: _ClassVar[int] + ITEM_COUNT_FIELD_NUMBER: _ClassVar[int] + TERMS_ACCEPTED_FIELD_NUMBER: _ClassVar[int] + user_name: str + user_contact: str + card_number: str + expiration_date: str + cvv: str + item_count: int + terms_accepted: bool + def __init__(self, user_name: _Optional[str] = ..., user_contact: _Optional[str] = ..., card_number: _Optional[str] = ..., expiration_date: _Optional[str] = ..., cvv: _Optional[str] = ..., item_count: _Optional[int] = ..., terms_accepted: bool = ...) -> None: ... + +class TransactionVerificationResponse(_message.Message): + __slots__ = ("is_valid", "message") + IS_VALID_FIELD_NUMBER: _ClassVar[int] + MESSAGE_FIELD_NUMBER: _ClassVar[int] + is_valid: bool + message: str + def __init__(self, is_valid: bool = ..., message: _Optional[str] = ...) -> None: ... diff --git a/utils/pb/transaction_verification/transaction_verification_pb2_grpc.py b/utils/pb/transaction_verification/transaction_verification_pb2_grpc.py new file mode 100644 index 000000000..32d7e091c --- /dev/null +++ b/utils/pb/transaction_verification/transaction_verification_pb2_grpc.py @@ -0,0 +1,66 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc + +import transaction_verification_pb2 as transaction__verification__pb2 + + +class TransactionVerificationServiceStub(object): + """Missing associated documentation comment in .proto file.""" + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.VerifyTransaction = channel.unary_unary( + '/transaction_verification.TransactionVerificationService/VerifyTransaction', + request_serializer=transaction__verification__pb2.TransactionVerificationRequest.SerializeToString, + response_deserializer=transaction__verification__pb2.TransactionVerificationResponse.FromString, + ) + + +class TransactionVerificationServiceServicer(object): + """Missing associated documentation comment in .proto file.""" + + def VerifyTransaction(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_TransactionVerificationServiceServicer_to_server(servicer, server): + rpc_method_handlers = { + 'VerifyTransaction': grpc.unary_unary_rpc_method_handler( + servicer.VerifyTransaction, + request_deserializer=transaction__verification__pb2.TransactionVerificationRequest.FromString, + response_serializer=transaction__verification__pb2.TransactionVerificationResponse.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'transaction_verification.TransactionVerificationService', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + + + # This class is part of an EXPERIMENTAL API. +class TransactionVerificationService(object): + """Missing associated documentation comment in .proto file.""" + + @staticmethod + def VerifyTransaction(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/transaction_verification.TransactionVerificationService/VerifyTransaction', + transaction__verification__pb2.TransactionVerificationRequest.SerializeToString, + transaction__verification__pb2.TransactionVerificationResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata)