removed authorize stuff
This commit is contained in:
81
README.md
81
README.md
@@ -1,3 +1,80 @@
|
|||||||
# Eamco Playground
|
# EAMCO Playground
|
||||||
|
|
||||||
This is a FastAPI project for Eamco playground application.
|
**EAMCO Playground** is a non-production, sandboxed microservice used for development, testing, and experimentation. It provides a safe and isolated environment for prototyping new features and refining complex algorithms before they are integrated into the core production services.
|
||||||
|
|
||||||
|
Think of this as the R&D department's workshop. It often contains experimental or alternative versions of logic found in other services like `eamco_auto_api`.
|
||||||
|
|
||||||
|
[](https://www.python.org/)
|
||||||
|
[](https://fastapi.tiangolo.com/)
|
||||||
|
[](https://www.postgresql.org/)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
|
||||||
|
The primary purpose of the Playground is to facilitate development and testing without affecting live data. Key use cases include:
|
||||||
|
|
||||||
|
- **Algorithm Refinement**: Testing new versions of the "K-Factor" (house consumption factor) calculation, as seen in `app/script/update_auto.py`.
|
||||||
|
- **New Integrations**: Evaluating new third-party services, such as testing the `pyowm` library as an alternative for weather data.
|
||||||
|
- **API Simulation**: Mimicking the endpoints of other microservices to test interactions and data flow in a controlled environment.
|
||||||
|
- **Debugging**: Providing a space to replicate and diagnose complex issues found in the production system without the risk of data corruption.
|
||||||
|
|
||||||
|
**This service should NOT be deployed to a production environment.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Key Components
|
||||||
|
|
||||||
|
- **Experimental Scripts**: The `app/script/` directory contains experimental logic. For example, `update_auto.py` holds a specific implementation of the K-Factor refinement algorithm.
|
||||||
|
- **Weather API Integration**: Uses the `pyowm` library to connect to the OpenWeatherMap API, likely for testing purposes.
|
||||||
|
- **Simulated API Routers**: Includes routers for `/main`, `/delivery`, `/confirm`, and `/info` that mirror the structure of other services, allowing developers to test frontend interactions or service-to-service communication.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
- Python 3.10+
|
||||||
|
- A PostgreSQL database (preferably a dedicated development or test instance).
|
||||||
|
|
||||||
|
### Installation
|
||||||
|
|
||||||
|
1. **Clone the repository and navigate into it.**
|
||||||
|
|
||||||
|
2. **Create a virtual environment and install dependencies:**
|
||||||
|
```bash
|
||||||
|
python -m venv venv
|
||||||
|
source venv/bin/activate
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Configure your environment:**
|
||||||
|
The application's configuration is managed by environment variables set in `settings_local.py`, `settings_dev.py`, or `settings_prod.py`. Ensure your database connection URI and any API keys (like for OpenWeatherMap) are set correctly in your chosen settings file.
|
||||||
|
|
||||||
|
### Running the Service
|
||||||
|
|
||||||
|
This service is intended for development use.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export MODE=DEVELOPMENT
|
||||||
|
uvicorn main:app --reload --host 0.0.0.0 --port <your_port>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
eamco_playground/
|
||||||
|
├── app/
|
||||||
|
│ ├── models/ # SQLAlchemy ORM models
|
||||||
|
│ ├── routers/ # API endpoint definitions for simulating other services
|
||||||
|
│ ├── schema/ # Pydantic models
|
||||||
|
│ └── script/ # Experimental scripts and algorithms
|
||||||
|
├── config.py # Logic for loading environment-specific settings
|
||||||
|
├── database.py # SQLAlchemy engine and session setup
|
||||||
|
├── main.py # FastAPI application entry point
|
||||||
|
├── requirements.txt # Python dependencies
|
||||||
|
└── README.md # This file
|
||||||
|
```
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
|
|
||||||
from fastapi import APIRouter, Request
|
from fastapi import APIRouter, Request, Depends
|
||||||
from datetime import date
|
from datetime import date
|
||||||
from database import session
|
from sqlalchemy.orm import Session
|
||||||
|
from database import get_db
|
||||||
from pyowm import OWM
|
from pyowm import OWM
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
from app.models.auto import Auto_Delivery, Tickets_Auto_Delivery
|
from app.models.auto import Auto_Delivery, Tickets_Auto_Delivery
|
||||||
@@ -19,14 +20,14 @@ router = APIRouter(
|
|||||||
|
|
||||||
|
|
||||||
@router.put("/auto/update/{autoid}")
|
@router.put("/auto/update/{autoid}")
|
||||||
async def update_auto(autoid: int, request: Request):
|
async def update_auto(autoid: int, request: Request, db: Session = Depends(get_db)):
|
||||||
|
|
||||||
request_body = await request.json()
|
request_body = await request.json()
|
||||||
gallons_delivered = request_body['gallons_delivered']
|
gallons_delivered = request_body['gallons_delivered']
|
||||||
gallons_delivered = Decimal(gallons_delivered)
|
gallons_delivered = Decimal(gallons_delivered)
|
||||||
|
|
||||||
|
|
||||||
get_auto_delivery = (session
|
get_auto_delivery = (db
|
||||||
.query(Auto_Delivery)
|
.query(Auto_Delivery)
|
||||||
.filter(Auto_Delivery.id == autoid)
|
.filter(Auto_Delivery.id == autoid)
|
||||||
.first())
|
.first())
|
||||||
@@ -50,9 +51,9 @@ async def update_auto(autoid: int, request: Request):
|
|||||||
get_auto_delivery.auto_status = 1
|
get_auto_delivery.auto_status = 1
|
||||||
get_auto_delivery.days_since_last_fill = 0
|
get_auto_delivery.days_since_last_fill = 0
|
||||||
|
|
||||||
session.add(get_auto_delivery)
|
db.add(get_auto_delivery)
|
||||||
|
|
||||||
session.commit()
|
db.commit()
|
||||||
|
|
||||||
return ({"ok": True}), 200
|
return ({"ok": True}), 200
|
||||||
|
|
||||||
@@ -60,7 +61,7 @@ async def update_auto(autoid: int, request: Request):
|
|||||||
|
|
||||||
|
|
||||||
@router.post("/auto/create/{autoid}")
|
@router.post("/auto/create/{autoid}")
|
||||||
async def create_auto_ticket(autoid: int, request: Request):
|
async def create_auto_ticket(autoid: int, request: Request, db: Session = Depends(get_db)):
|
||||||
|
|
||||||
|
|
||||||
request_body = await request.json()
|
request_body = await request.json()
|
||||||
@@ -68,11 +69,11 @@ async def create_auto_ticket(autoid: int, request: Request):
|
|||||||
gallons_delivered = Decimal(gallons_delivered)
|
gallons_delivered = Decimal(gallons_delivered)
|
||||||
|
|
||||||
|
|
||||||
get_auto_delivery = (session
|
get_auto_delivery = (db
|
||||||
.query(Auto_Delivery)
|
.query(Auto_Delivery)
|
||||||
.filter(Auto_Delivery.id == autoid)
|
.filter(Auto_Delivery.id == autoid)
|
||||||
.first())
|
.first())
|
||||||
get_todays_price = (session.query(Pricing_Oil_Oil)
|
get_todays_price = (db.query(Pricing_Oil_Oil)
|
||||||
.order_by(Pricing_Oil_Oil.id.desc())
|
.order_by(Pricing_Oil_Oil.id.desc())
|
||||||
.first())
|
.first())
|
||||||
gallons_put_in_home = Decimal(gallons_delivered)
|
gallons_put_in_home = Decimal(gallons_delivered)
|
||||||
@@ -95,9 +96,9 @@ async def create_auto_ticket(autoid: int, request: Request):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
session.add(create_new_ticket)
|
db.add(create_new_ticket)
|
||||||
|
|
||||||
session.commit()
|
db.commit()
|
||||||
|
|
||||||
return ({
|
return ({
|
||||||
"ok": True,
|
"ok": True,
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
from fastapi import APIRouter, Request, Depends
|
from fastapi import APIRouter, Request, Depends
|
||||||
from fastapi.responses import JSONResponse
|
from fastapi.responses import JSONResponse
|
||||||
from fastapi.encoders import jsonable_encoder
|
from fastapi.encoders import jsonable_encoder
|
||||||
from database import session
|
from sqlalchemy.orm import Session
|
||||||
|
from database import get_db
|
||||||
|
|
||||||
from app.models.auto import Auto_Delivery, Tickets_Auto_Delivery
|
from app.models.auto import Auto_Delivery, Tickets_Auto_Delivery
|
||||||
from app.models.delivery import Delivery
|
from app.models.delivery import Delivery
|
||||||
@@ -17,9 +18,9 @@ router = APIRouter(
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/past/{customer_id}", status_code=200)
|
@router.get("/past/{customer_id}", status_code=200)
|
||||||
def past(customer_id: int):
|
def past(customer_id: int, db: Session = Depends(get_db)):
|
||||||
|
|
||||||
get_customer_past_delivery = (session
|
get_customer_past_delivery = (db
|
||||||
.query(Delivery)
|
.query(Delivery)
|
||||||
.filter(Delivery.customer_id == customer_id)
|
.filter(Delivery.customer_id == customer_id)
|
||||||
.order_by(Delivery.id.desc())
|
.order_by(Delivery.id.desc())
|
||||||
@@ -30,11 +31,11 @@ def past(customer_id: int):
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/all/customers", status_code=200)
|
@router.get("/all/customers", status_code=200)
|
||||||
def get_delivery_customers():
|
def get_delivery_customers(db: Session = Depends(get_db)):
|
||||||
|
|
||||||
|
|
||||||
automatics = (
|
automatics = (
|
||||||
session.query(Auto_Delivery)
|
db.query(Auto_Delivery)
|
||||||
.filter(Auto_Delivery.auto_status == 1)
|
.filter(Auto_Delivery.auto_status == 1)
|
||||||
.order_by(Auto_Delivery.estimated_gallons_left.asc())
|
.order_by(Auto_Delivery.estimated_gallons_left.asc())
|
||||||
.all()
|
.all()
|
||||||
@@ -45,9 +46,9 @@ def get_delivery_customers():
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/driver/{driver_employee_id}", status_code=200)
|
@router.get("/driver/{driver_employee_id}", status_code=200)
|
||||||
def get_delivery_for_specific_driver(driver_employee_id: int):
|
def get_delivery_for_specific_driver(driver_employee_id: int, db: Session = Depends(get_db)):
|
||||||
automatics = (
|
automatics = (
|
||||||
session.query(Delivery)
|
db.query(Delivery)
|
||||||
.filter(Delivery.driver_employee_id == driver_employee_id)
|
.filter(Delivery.driver_employee_id == driver_employee_id)
|
||||||
.filter(Delivery.automatic == 1)
|
.filter(Delivery.automatic == 1)
|
||||||
.filter(Delivery.delivery_status == 0)
|
.filter(Delivery.delivery_status == 0)
|
||||||
@@ -58,10 +59,10 @@ def get_delivery_for_specific_driver(driver_employee_id: int):
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/{auto_id}", status_code=200)
|
@router.get("/{auto_id}", status_code=200)
|
||||||
def get_auto(auto_id):
|
def get_auto(auto_id, db: Session = Depends(get_db)):
|
||||||
|
|
||||||
get_delivery = (
|
get_delivery = (
|
||||||
session.query(Auto_Delivery)
|
db.query(Auto_Delivery)
|
||||||
.filter(Auto_Delivery.id == auto_id)
|
.filter(Auto_Delivery.id == auto_id)
|
||||||
.first()
|
.first()
|
||||||
)
|
)
|
||||||
@@ -71,10 +72,10 @@ def get_auto(auto_id):
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/delivery/{delivery_id_order}", status_code=200)
|
@router.get("/delivery/{delivery_id_order}", status_code=200)
|
||||||
def get_delivery(delivery_id_order):
|
def get_delivery(delivery_id_order, db: Session = Depends(get_db)):
|
||||||
|
|
||||||
get_delivery = (
|
get_delivery = (
|
||||||
session.query(Tickets_Auto_Delivery)
|
db.query(Tickets_Auto_Delivery)
|
||||||
.filter(Tickets_Auto_Delivery.id == delivery_id_order)
|
.filter(Tickets_Auto_Delivery.id == delivery_id_order)
|
||||||
.first()
|
.first()
|
||||||
)
|
)
|
||||||
@@ -83,10 +84,10 @@ def get_delivery(delivery_id_order):
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/all/profile/{customer_id}", status_code=200)
|
@router.get("/all/profile/{customer_id}", status_code=200)
|
||||||
def get_autos_customers(customer_id):
|
def get_autos_customers(customer_id, db: Session = Depends(get_db)):
|
||||||
|
|
||||||
get_delivery = (
|
get_delivery = (
|
||||||
session.query(Tickets_Auto_Delivery)
|
db.query(Tickets_Auto_Delivery)
|
||||||
.filter(Tickets_Auto_Delivery.customer_id == customer_id)
|
.filter(Tickets_Auto_Delivery.customer_id == customer_id)
|
||||||
.order_by(Tickets_Auto_Delivery.id.asc())
|
.order_by(Tickets_Auto_Delivery.id.asc())
|
||||||
.all()
|
.all()
|
||||||
|
|||||||
68
app/routers/info.py
Normal file
68
app/routers/info.py
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
from fastapi import APIRouter, Depends
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
from datetime import datetime
|
||||||
|
from app.models.customer import Customer_Customer
|
||||||
|
from app.models.delivery import Delivery
|
||||||
|
from database import get_db
|
||||||
|
from sqlalchemy import func
|
||||||
|
|
||||||
|
router = APIRouter(
|
||||||
|
prefix="/info",
|
||||||
|
tags=["info"],
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_customers_without_delivery_since(db: Session, date_threshold: datetime):
|
||||||
|
"""
|
||||||
|
Queries all customers who haven't received a delivery since the specified date.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
db: The database session.
|
||||||
|
date_threshold: The date threshold. Customers whose last delivery is
|
||||||
|
before this date (or have no deliveries) will be returned.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A tuple containing the count and a list of Customer_Customer objects.
|
||||||
|
"""
|
||||||
|
# Subquery to find the last delivery date for each customer
|
||||||
|
last_delivery_subquery = (
|
||||||
|
db.query(
|
||||||
|
Delivery.customer_id,
|
||||||
|
func.max(Delivery.when_delivered).label("last_delivery_date")
|
||||||
|
)
|
||||||
|
.group_by(Delivery.customer_id)
|
||||||
|
.subquery()
|
||||||
|
)
|
||||||
|
|
||||||
|
# Main query to join customers with their last delivery date
|
||||||
|
# and filter based on the date threshold
|
||||||
|
customers = (
|
||||||
|
db.query(Customer_Customer)
|
||||||
|
.outerjoin(
|
||||||
|
last_delivery_subquery,
|
||||||
|
Customer_Customer.id == last_delivery_subquery.c.customer_id
|
||||||
|
)
|
||||||
|
.filter(
|
||||||
|
(last_delivery_subquery.c.last_delivery_date < date_threshold) |
|
||||||
|
(last_delivery_subquery.c.last_delivery_date == None)
|
||||||
|
)
|
||||||
|
.all()
|
||||||
|
)
|
||||||
|
return len(customers), customers
|
||||||
|
|
||||||
|
@router.get("/customers/no_delivery_since/{year}/{month}/{day}")
|
||||||
|
async def customers_no_delivery_since(
|
||||||
|
year: int,
|
||||||
|
month: int,
|
||||||
|
day: int,
|
||||||
|
db: Session = Depends(get_db)
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Get customers who have not had a delivery since the specified date.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
threshold_date = datetime(year, month, day).date()
|
||||||
|
count, customers = get_customers_without_delivery_since(db, threshold_date)
|
||||||
|
return {"customer_count": count, "customers": customers}
|
||||||
|
except ValueError as e:
|
||||||
|
return {"error": str(e)}
|
||||||
|
|
||||||
@@ -1,31 +1,15 @@
|
|||||||
## File: app/routers/main.py (or your equivalent path)
|
## File: app/routers/main.py (or your equivalent path)
|
||||||
|
|
||||||
from fastapi import APIRouter, HTTPException
|
from fastapi import APIRouter, HTTPException, Depends
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
from sqlalchemy import func
|
from sqlalchemy import func
|
||||||
|
|
||||||
|
|
||||||
# Your existing imports
|
# Your existing imports
|
||||||
from database import session
|
from database import get_db
|
||||||
from pyowm import OWM
|
from pyowm import OWM
|
||||||
|
|
||||||
from app.models.customer import Customer_Customer
|
|
||||||
from app.models.cards import Card_Card
|
|
||||||
# Imports needed for the migration task.
|
|
||||||
# YOU MUST ADJUST THESE IMPORTS TO MATCH YOUR PROJECT'S ACTUAL STRUCTURE.
|
|
||||||
from database import session # Assuming your SessionLocal is here for a clean session
|
|
||||||
from authorizenet import apicontractsv1
|
|
||||||
from authorizenet.constants import constants
|
|
||||||
from authorizenet.apicontrollers import (
|
|
||||||
createCustomerProfileController,
|
|
||||||
createCustomerPaymentProfileController
|
|
||||||
|
|
||||||
)
|
|
||||||
from config import load_config # Assuming you have this
|
|
||||||
|
|
||||||
# Load Authorize.net credentials
|
|
||||||
ApplicationConfig = load_config()
|
|
||||||
# --- ROUTER DEFINITION ---
|
# --- ROUTER DEFINITION ---
|
||||||
|
|
||||||
router = APIRouter(
|
router = APIRouter(
|
||||||
@@ -33,323 +17,3 @@ router = APIRouter(
|
|||||||
tags=["main"],
|
tags=["main"],
|
||||||
responses={404: {"description": "Not found"}},
|
responses={404: {"description": "Not found"}},
|
||||||
)
|
)
|
||||||
|
|
||||||
if ApplicationConfig.CURRENT_SETTINGS == 'PRODUCTION':
|
|
||||||
constants.environment = constants.PRODUCTION
|
|
||||||
VALIDATION_MODE = "liveMode"
|
|
||||||
API_LOGIN_ID = ApplicationConfig.API_LOGIN_ID
|
|
||||||
TRANSACTION_KEY = ApplicationConfig.TRANSACTION_KEY
|
|
||||||
else:
|
|
||||||
constants.environment = constants.SANDBOX
|
|
||||||
constants.show_url_on_request = True
|
|
||||||
VALIDATION_MODE = "testMode"
|
|
||||||
API_LOGIN_ID = ApplicationConfig.API_LOGIN_ID
|
|
||||||
TRANSACTION_KEY = ApplicationConfig.TRANSACTION_KEY
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
state_abbrevs = ['MA', 'RI', 'NH', 'ME', 'VT', 'CT', 'NY']
|
|
||||||
|
|
||||||
@router.post("/maintenance/migrate-cards-now")
|
|
||||||
def run_card_migration_synchronously():
|
|
||||||
"""
|
|
||||||
Triggers a one-time, SYNCHRONOUS task to migrate all existing, insecurely
|
|
||||||
stored credit cards from the `card_card` table to secure Authorize.Net CIM profiles.
|
|
||||||
This function is self-contained and does not use `crud` or `schemas`.
|
|
||||||
|
|
||||||
WARNING: This is a long-running process and the request may time out.
|
|
||||||
Monitor server logs for completion. This endpoint should be removed after use.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def _create_authnet_profile(customer_obj: Customer_Customer, card_obj: Card_Card):
|
|
||||||
"""Helper to create a new Authorize.Net Customer Profile."""
|
|
||||||
merchantAuth = apicontractsv1.merchantAuthenticationType(name=API_LOGIN_ID, transactionKey=TRANSACTION_KEY)
|
|
||||||
expiration_date_str = f"{card_obj.expiration_year}-{str(card_obj.expiration_month).zfill(2)}"
|
|
||||||
|
|
||||||
# Validate expiration
|
|
||||||
month = None
|
|
||||||
year = None
|
|
||||||
if card_obj.expiration_month:
|
|
||||||
try:
|
|
||||||
month = int(card_obj.expiration_month)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
if card_obj.expiration_year:
|
|
||||||
try:
|
|
||||||
year = int(card_obj.expiration_year)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
if not year or not month or month < 1 or month > 12 or year < 2000 or year > 2050:
|
|
||||||
raise ValueError(f"Invalid expiration year={card_obj.expiration_year}, month={card_obj.expiration_month} for card ID {card_obj.id}")
|
|
||||||
expiration_date_str = f"{year:04d}-{month:02d}"
|
|
||||||
# // FIX 1: Sanitize card number by removing all non-digit characters
|
|
||||||
sanitized_card_number = ''.join(filter(str.isdigit, str(card_obj.card_number)))
|
|
||||||
if len(sanitized_card_number) < 13 or len(sanitized_card_number) > 19 or not sanitized_card_number.isdigit():
|
|
||||||
raise ValueError(f"Invalid card number length {len(sanitized_card_number)} or non-numeric for card ID {card_obj.id}")
|
|
||||||
# Sanitize CVV
|
|
||||||
cardCode = str(card_obj.security_number).strip()
|
|
||||||
if len(cardCode) < 3 or len(cardCode) > 4 or not cardCode.isdigit():
|
|
||||||
raise ValueError(f"Invalid CVV length {len(cardCode)} or non-numeric for card ID {card_obj.id}")
|
|
||||||
# Get dynamic state
|
|
||||||
the_state = state_abbrevs[customer_obj.customer_state] if 0 <= customer_obj.customer_state < len(state_abbrevs) else 'MA'
|
|
||||||
|
|
||||||
# Debug print
|
|
||||||
print(f"DEBUG SEND CREATE: cardNumber='{sanitized_card_number}', expirationDate='{expiration_date_str}', cardCode='{cardCode}', customerID={customer_obj.id}, name='{customer_obj.customer_first_name} {customer_obj.customer_last_name}', address='{customer_obj.customer_address}', city='{customer_obj.customer_town}', state='{the_state}', zip='{customer_obj.customer_zip}', country='USA', phone='{customer_obj.customer_phone_number}', email='{customer_obj.customer_email}'")
|
|
||||||
|
|
||||||
creditCard = apicontractsv1.creditCardType(
|
|
||||||
cardNumber=sanitized_card_number,
|
|
||||||
expirationDate=expiration_date_str,
|
|
||||||
cardCode=cardCode
|
|
||||||
)
|
|
||||||
billTo = apicontractsv1.customerAddressType(
|
|
||||||
firstName=customer_obj.customer_first_name,
|
|
||||||
lastName=customer_obj.customer_last_name,
|
|
||||||
address=customer_obj.customer_address,
|
|
||||||
city=customer_obj.customer_town,
|
|
||||||
state=the_state,
|
|
||||||
zip=customer_obj.customer_zip,
|
|
||||||
country="USA",
|
|
||||||
phoneNumber=customer_obj.customer_phone_number
|
|
||||||
)
|
|
||||||
paymentProfile = apicontractsv1.customerPaymentProfileType(
|
|
||||||
billTo=billTo,
|
|
||||||
payment=apicontractsv1.paymentType(creditCard=creditCard),
|
|
||||||
defaultPaymentProfile=True
|
|
||||||
)
|
|
||||||
customerProfile = apicontractsv1.customerProfileType(
|
|
||||||
merchantCustomerId=str(customer_obj.id),
|
|
||||||
email=customer_obj.customer_email,
|
|
||||||
paymentProfiles=[paymentProfile]
|
|
||||||
)
|
|
||||||
request = apicontractsv1.createCustomerProfileRequest(
|
|
||||||
merchantAuthentication=merchantAuth,
|
|
||||||
profile=customerProfile,
|
|
||||||
validationMode="testMode"
|
|
||||||
)
|
|
||||||
controller = createCustomerProfileController(request)
|
|
||||||
if ApplicationConfig.CURRENT_SETTINGS == 'PRODUCTION':
|
|
||||||
controller.setenvironment(constants.PRODUCTION)
|
|
||||||
controller.execute()
|
|
||||||
elif ApplicationConfig.CURRENT_SETTINGS == 'LOCAL':
|
|
||||||
controller.setenvironment(constants.PRODUCTION)
|
|
||||||
controller.execute()
|
|
||||||
else:
|
|
||||||
controller.execute()
|
|
||||||
response = controller.getresponse()
|
|
||||||
|
|
||||||
if response.messages.resultCode == "Ok":
|
|
||||||
profile_id = response.customerProfileId if hasattr(response, 'customerProfileId') else None
|
|
||||||
payment_ids = response.customerPaymentProfileIdList.numericString if hasattr(response, 'customerPaymentProfileIdList') and hasattr(response.customerPaymentProfileIdList, 'numericString') else []
|
|
||||||
payment_id = payment_ids[0] if payment_ids else None
|
|
||||||
return str(profile_id), str(payment_id) if payment_id else None
|
|
||||||
else:
|
|
||||||
# Handle errors
|
|
||||||
error_msg = "Unknown Authorize.Net Error"
|
|
||||||
if response is not None and hasattr(response, 'messages') and response.messages is not None and hasattr(response.messages, 'message') and response.messages.message:
|
|
||||||
msg = response.messages.message[0] if isinstance(response.messages.message, list) else response.messages.message
|
|
||||||
if msg and hasattr(msg, 'text') and msg.text is not None:
|
|
||||||
if isinstance(msg.text, str):
|
|
||||||
error_msg = msg.text
|
|
||||||
else:
|
|
||||||
# Handle case where text might be an object with 'text' attribute
|
|
||||||
error_msg = msg.text.text if hasattr(msg.text, 'text') else str(msg.text)
|
|
||||||
print(f" AUTH.NET ERROR: {error_msg}")
|
|
||||||
return None, None
|
|
||||||
|
|
||||||
def _add_authnet_payment_profile(profile_id: str, customer_obj: Customer_Customer, card_obj: Card_Card):
|
|
||||||
"""Helper to add a new Payment Profile to an existing Customer Profile."""
|
|
||||||
merchantAuth = apicontractsv1.merchantAuthenticationType(name=API_LOGIN_ID, transactionKey=TRANSACTION_KEY)
|
|
||||||
expiration_date_str = f"{card_obj.expiration_year}-{str(card_obj.expiration_month).zfill(2)}"
|
|
||||||
# State abbreviations list
|
|
||||||
|
|
||||||
# // FIX 1 (Applied here as well): Sanitize card number
|
|
||||||
sanitized_card_number = ''.join(filter(str.isdigit, str(card_obj.card_number)))
|
|
||||||
|
|
||||||
creditCard = apicontractsv1.creditCardType(
|
|
||||||
cardNumber=sanitized_card_number,
|
|
||||||
expirationDate=expiration_date_str,
|
|
||||||
cardCode=str(card_obj.security_number).strip()
|
|
||||||
)
|
|
||||||
# Get dynamic state
|
|
||||||
the_state = state_abbrevs[customer_obj.customer_state] if 0 <= customer_obj.customer_state < len(state_abbrevs) else 'MA'
|
|
||||||
|
|
||||||
billTo = apicontractsv1.customerAddressType(
|
|
||||||
firstName=customer_obj.customer_first_name,
|
|
||||||
lastName=customer_obj.customer_last_name,
|
|
||||||
address=customer_obj.customer_address,
|
|
||||||
city=customer_obj.customer_town,
|
|
||||||
state=the_state,
|
|
||||||
zip=customer_obj.customer_zip,
|
|
||||||
country="USA",
|
|
||||||
phoneNumber=customer_obj.customer_phone_number
|
|
||||||
)
|
|
||||||
paymentProfile = apicontractsv1.customerPaymentProfileType(
|
|
||||||
billTo=billTo,
|
|
||||||
payment=apicontractsv1.paymentType(creditCard=creditCard),
|
|
||||||
defaultPaymentProfile=False
|
|
||||||
)
|
|
||||||
request = apicontractsv1.createCustomerPaymentProfileRequest(
|
|
||||||
merchantAuthentication=merchantAuth,
|
|
||||||
customerProfileId=profile_id,
|
|
||||||
paymentProfile=paymentProfile,
|
|
||||||
validationMode="testMode"
|
|
||||||
)
|
|
||||||
controller = createCustomerPaymentProfileController(request)
|
|
||||||
if ApplicationConfig.CURRENT_SETTINGS == 'PRODUCTION':
|
|
||||||
controller.setenvironment(constants.PRODUCTION)
|
|
||||||
controller.execute()
|
|
||||||
elif ApplicationConfig.CURRENT_SETTINGS == 'LOCAL':
|
|
||||||
controller.setenvironment(constants.PRODUCTION)
|
|
||||||
controller.execute()
|
|
||||||
else:
|
|
||||||
controller.execute()
|
|
||||||
|
|
||||||
response = controller.getresponse()
|
|
||||||
if response.messages.resultCode == "Ok":
|
|
||||||
return str(response.customerPaymentProfileId)
|
|
||||||
else:
|
|
||||||
# // FIX 2 (Applied here as well): Robust error message parsing
|
|
||||||
error_msg = "Unknown Authorize.Net Error"
|
|
||||||
if response is not None and hasattr(response, 'messages') and response.messages is not None and hasattr(response.messages, 'message') and response.messages.message:
|
|
||||||
msg = response.messages.message[0]
|
|
||||||
if hasattr(msg, 'text') and msg.text is not None:
|
|
||||||
error_msg = msg.text.text
|
|
||||||
print(f" AUTH.NET ERROR: {error_msg}")
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# --- MIGRATION SCRIPT LOGIC ---
|
|
||||||
print("="*60)
|
|
||||||
print("MIGRATION STARTED: Migrating all customer cards to Authorize.Net CIM.")
|
|
||||||
print(f"Start Time: {datetime.now()}")
|
|
||||||
print("="*60)
|
|
||||||
|
|
||||||
customers_processed = 0
|
|
||||||
cards_migrated = 0
|
|
||||||
error_count = 0
|
|
||||||
|
|
||||||
try:
|
|
||||||
customers_to_migrate = (session
|
|
||||||
.query(Customer_Customer)
|
|
||||||
.filter(Customer_Customer.auth_net_profile_id == None)
|
|
||||||
.all())
|
|
||||||
|
|
||||||
total_customers = len(customers_to_migrate)
|
|
||||||
print(f"Found {total_customers} customers to migrate.")
|
|
||||||
|
|
||||||
for index, customer in enumerate(customers_to_migrate):
|
|
||||||
customers_processed += 1
|
|
||||||
print(f"\n--- Processing Customer {index + 1}/{total_customers} (ID: {customer.id}, Name: {customer.customer_first_name}) ---")
|
|
||||||
|
|
||||||
insecure_cards = (session
|
|
||||||
.query(Card_Card)
|
|
||||||
.filter(Card_Card.user_id == customer.id)
|
|
||||||
.all())
|
|
||||||
|
|
||||||
print(f"DEBUG: Customer {customer.id} has {len(insecure_cards)} cards")
|
|
||||||
customer_profile_id_for_session = None
|
|
||||||
|
|
||||||
# # Check for existing profile
|
|
||||||
# existing_profile, existing_payment_ids = _get_existing_profile(str(customer.id))
|
|
||||||
# if existing_profile and existing_payment_ids:
|
|
||||||
# print(f" -> Found existing Authorize.Net profile ID {existing_profile}")
|
|
||||||
# customer.auth_net_profile_id = existing_profile
|
|
||||||
# session.add(customer)
|
|
||||||
# session.commit()
|
|
||||||
# for i, card_data in enumerate(insecure_cards):
|
|
||||||
# if i < len(existing_payment_ids):
|
|
||||||
# display_card_number = ''.join(filter(str.isdigit, str(card_data.card_number)))
|
|
||||||
# if not display_card_number:
|
|
||||||
# print(f" SKIPPING CARD: Card number is empty after sanitization for card ID {card_data.id}")
|
|
||||||
# error_count += 1
|
|
||||||
# continue
|
|
||||||
# card_data.auth_net_payment_profile_id = existing_payment_ids[i]
|
|
||||||
# session.add(card_data)
|
|
||||||
# session.commit()
|
|
||||||
# print(f" SUCCESS: Mapped existing Payment ID {existing_payment_ids[i]} to card ending ...{display_card_number[-4:]}")
|
|
||||||
# cards_migrated += 1
|
|
||||||
# else:
|
|
||||||
# print(f" WARNING: No existing payment profile Kawasaki for card {card_data.id}")
|
|
||||||
# error_count += 1
|
|
||||||
# continue # skip creation loop
|
|
||||||
|
|
||||||
for i, card_data in enumerate(insecure_cards):
|
|
||||||
try:
|
|
||||||
# Basic data validation before sending
|
|
||||||
if not card_data.card_number or not card_data.expiration_year or not card_data.expiration_month:
|
|
||||||
print(f" SKIPPING CARD: Incomplete card data for card ID {card_data.id}.")
|
|
||||||
error_count += 1
|
|
||||||
continue
|
|
||||||
|
|
||||||
print(f"DEBUG RAW: Card ID {card_data.id}: number='{card_data.card_number}', year='{card_data.expiration_year}', month='{card_data.expiration_month}', cvv='{card_data.security_number}', name='{customer.customer_first_name} {customer.customer_last_name}'")
|
|
||||||
|
|
||||||
# Sanitize card number for display
|
|
||||||
display_card_number = ''.join(filter(str.isdigit, str(card_data.card_number)))
|
|
||||||
|
|
||||||
if not display_card_number:
|
|
||||||
print(f" SKIPPING CARD: Card number is empty after sanitization for card ID {card_data.id}.")
|
|
||||||
error_count += 1
|
|
||||||
continue
|
|
||||||
|
|
||||||
if i == 0:
|
|
||||||
print(f" -> Creating new CIM profile with card ending in ...{display_card_number[-4:]}")
|
|
||||||
profile_id, payment_id = _create_authnet_profile(customer, card_data)
|
|
||||||
|
|
||||||
if profile_id and payment_id:
|
|
||||||
customer.auth_net_profile_id = profile_id
|
|
||||||
session.add(customer)
|
|
||||||
|
|
||||||
customer_profile_id_for_session = profile_id
|
|
||||||
|
|
||||||
card_data.auth_net_payment_profile_id = payment_id
|
|
||||||
session.add(card_data)
|
|
||||||
session.commit()
|
|
||||||
|
|
||||||
print(f" SUCCESS: Created Profile ID {profile_id} and Payment ID {payment_id}")
|
|
||||||
cards_migrated += 1
|
|
||||||
else:
|
|
||||||
print(f" ERROR: Failed to create profile for customer {customer.id}.")
|
|
||||||
error_count += 1
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
if not display_card_number:
|
|
||||||
print(f" SKIPPING CARD: Card number is empty after sanitization for card ID {card_data.id}.")
|
|
||||||
error_count += 1
|
|
||||||
continue
|
|
||||||
print(f" -> Adding additional card ending in ...{display_card_number[-4:]}")
|
|
||||||
payment_id = _add_authnet_payment_profile(customer_profile_id_for_session, customer, card_data)
|
|
||||||
if payment_id:
|
|
||||||
card_data.auth_net_payment_profile_id = payment_id
|
|
||||||
session.add(card_data)
|
|
||||||
session.commit()
|
|
||||||
print(f" SUCCESS: Added Payment ID {payment_id}")
|
|
||||||
cards_migrated += 1
|
|
||||||
else:
|
|
||||||
print(f" ERROR: Failed to add additional card for customer {customer.id}.")
|
|
||||||
error_count += 1
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f" CRITICAL ERROR processing a card for customer {customer.id}: {str(e)}")
|
|
||||||
error_count += 1
|
|
||||||
session.rollback()
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"A critical error occurred during the migration process: {e}")
|
|
||||||
session.rollback()
|
|
||||||
return {"ok": False, "error": f"Migration failed with a critical error: {e}"}, 500
|
|
||||||
|
|
||||||
finally:
|
|
||||||
print("="*60)
|
|
||||||
print("MIGRATION FINISHED!")
|
|
||||||
print(f"End Time: {datetime.now()}")
|
|
||||||
print(f"Summary: Processed {customers_processed} customers, migrated {cards_migrated} cards, encountered {error_count} errors.")
|
|
||||||
print("="*60)
|
|
||||||
|
|
||||||
return {
|
|
||||||
"ok": True,
|
|
||||||
"message": "Migration process completed successfully.",
|
|
||||||
"customers_processed": customers_processed,
|
|
||||||
"cards_migrated": cards_migrated,
|
|
||||||
"errors": error_count,
|
|
||||||
}, 200
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
from database import session
|
from database import Session
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -23,4 +23,4 @@ def calc_home_factor(gallons_put_in_home, current_house_factor):
|
|||||||
customer_home_factor = Decimal(current_house_factor)
|
customer_home_factor = Decimal(current_house_factor)
|
||||||
if customer_home_factor <= 0:
|
if customer_home_factor <= 0:
|
||||||
customer_home_factor = Decimal(.25)
|
customer_home_factor = Decimal(.25)
|
||||||
return customer_home_factor
|
return customer_home_factor
|
||||||
|
|||||||
10
database.py
10
database.py
@@ -22,7 +22,13 @@ url = URL.create(
|
|||||||
engine = create_engine(url)
|
engine = create_engine(url)
|
||||||
|
|
||||||
Session = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
Session = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||||
session = Session()
|
|
||||||
|
|
||||||
Base = declarative_base()
|
Base = declarative_base()
|
||||||
Base.metadata.create_all(engine)
|
Base.metadata.create_all(engine)
|
||||||
|
|
||||||
|
def get_db():
|
||||||
|
db = Session()
|
||||||
|
try:
|
||||||
|
yield db
|
||||||
|
finally:
|
||||||
|
db.close()
|
||||||
3
main.py
3
main.py
@@ -1,5 +1,5 @@
|
|||||||
from fastapi import FastAPI
|
from fastapi import FastAPI
|
||||||
from app.routers import main, delivery, confirm
|
from app.routers import main, delivery, confirm, info
|
||||||
from fastapi.middleware.cors import CORSMiddleware
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
|
|
||||||
|
|
||||||
@@ -9,6 +9,7 @@ app = FastAPI()
|
|||||||
app.include_router(main.router)
|
app.include_router(main.router)
|
||||||
app.include_router(delivery.router)
|
app.include_router(delivery.router)
|
||||||
app.include_router(confirm.router)
|
app.include_router(confirm.router)
|
||||||
|
app.include_router(info.router)
|
||||||
|
|
||||||
origins = [
|
origins = [
|
||||||
"http://localhost:9000",
|
"http://localhost:9000",
|
||||||
|
|||||||
@@ -4,4 +4,3 @@ psycopg2-binary
|
|||||||
sqlalchemy
|
sqlalchemy
|
||||||
pyowm
|
pyowm
|
||||||
setuptools
|
setuptools
|
||||||
authorizenet
|
|
||||||
@@ -30,5 +30,3 @@ class ApplicationConfig:
|
|||||||
"http://localhost:9516", # Authorize service port
|
"http://localhost:9516", # Authorize service port
|
||||||
|
|
||||||
]
|
]
|
||||||
API_LOGIN_ID = '9U6w96gZmX'
|
|
||||||
TRANSACTION_KEY = '94s6Qy458mMNJr7G'
|
|
||||||
|
|||||||
Reference in New Issue
Block a user