You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
214 lines
7.3 KiB
214 lines
7.3 KiB
|
|
import pymysql
|
|
import sys
|
|
import json
|
|
import random
|
|
import threading
|
|
import logging
|
|
import stripe
|
|
from sqlalchemy import func
|
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
from datetime import datetime
|
|
from typing import List, Dict, Union, Any
|
|
from stripe_payment_processor import StripePaymentProcessor
|
|
from config import Config
|
|
from app import create_app, db
|
|
from models import Payments, PaymentBatch, SinglePayments, PaymentPlans
|
|
from splynx import Splynx, SPLYNX_URL, SPLYNX_KEY, SPLYNX_SECRET
|
|
|
|
|
|
api_key = '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'
|
|
stripe.api_key = api_key
|
|
|
|
|
|
|
|
def issue_refund_for_payment(payment_record, amount=None, reason=None):
|
|
"""
|
|
Issue a refund for a payment record using its Stripe_Charge_ID
|
|
|
|
Args:
|
|
payment_record: The Payments model instance
|
|
amount: Optional - amount to refund in cents. If None, refunds full amount
|
|
reason: Optional - reason for refund ('duplicate', 'fraudulent', 'requested_by_customer')
|
|
|
|
Returns:
|
|
dict: Success status and refund details
|
|
"""
|
|
try:
|
|
# Check if payment has a charge ID
|
|
if not payment_record.Stripe_Charge_ID:
|
|
return {
|
|
'success': False,
|
|
'error': 'No Stripe Charge ID found for this payment',
|
|
'payment_id': payment_record.id
|
|
}
|
|
|
|
# Check if already refunded
|
|
if payment_record.Refund:
|
|
return {
|
|
'success': False,
|
|
'error': 'Payment already marked as refunded',
|
|
'payment_id': payment_record.id
|
|
}
|
|
|
|
# Create refund parameters
|
|
refund_params = {
|
|
'charge': payment_record.Stripe_Charge_ID
|
|
}
|
|
|
|
if amount:
|
|
refund_params['amount'] = amount
|
|
|
|
if reason:
|
|
refund_params['reason'] = reason
|
|
|
|
# Issue the refund
|
|
refund = stripe.Refund.create(**refund_params)
|
|
|
|
if refund['status'] == "succeeded":
|
|
|
|
# Update the payment record
|
|
payment_record.Refund = True
|
|
payment_record.Stripe_Refund_ID = refund.id
|
|
payment_record.Stripe_Refund_Created = datetime.fromtimestamp(refund.created)
|
|
payment_record.Refund_JSON = json.dumps(refund)
|
|
|
|
db.session.commit()
|
|
|
|
return {
|
|
'success': True,
|
|
'refund_id': refund.id,
|
|
'amount_refunded': refund.amount,
|
|
'payment_id': payment_record.id,
|
|
'refund': refund
|
|
}
|
|
else:
|
|
payment_record.Refund = False
|
|
|
|
# Commit the changes
|
|
db.session.commit()
|
|
return {
|
|
'success': False,
|
|
'payment_id': payment_record.id,
|
|
'refund': refund
|
|
}
|
|
|
|
except stripe.error.InvalidRequestError as e:
|
|
if "has already been refunded" in str(e) and payment_record.Refund != True:
|
|
payment_record.Refund = True
|
|
payment_record.Stripe_Refund_Created = datetime.now()
|
|
db.session.commit()
|
|
return {
|
|
'success': False,
|
|
'error': f'Stripe error: {str(e)}',
|
|
'payment_id': payment_record.id
|
|
}
|
|
except Exception as e:
|
|
db.session.rollback()
|
|
return {
|
|
'success': False,
|
|
'error': f'Unexpected error: {str(e)}',
|
|
'payment_id': payment_record.id
|
|
}
|
|
|
|
if __name__ == "__main__":
|
|
app = create_app()
|
|
|
|
with app.app_context():
|
|
# Find customer_ids that appear more than once
|
|
duplicate_customer_ids = db.session.query(
|
|
Payments.Stripe_Customer_ID
|
|
).group_by(
|
|
Payments.Stripe_Customer_ID
|
|
).having(
|
|
func.count(Payments.Stripe_Customer_ID) > 1
|
|
).all()
|
|
|
|
# Get the actual duplicate records
|
|
duplicate_records = db.session.query(Payments).filter(
|
|
Payments.Stripe_Customer_ID.in_([row[0] for row in duplicate_customer_ids])
|
|
).order_by(Payments.Stripe_Customer_ID).all()
|
|
i = 0
|
|
has_charge = 0
|
|
for a in duplicate_records:
|
|
i += 1
|
|
#print(a.Stripe_Customer_ID)
|
|
if a.Stripe_Charge_ID != None:
|
|
has_charge += 1
|
|
|
|
print(i, i/2, has_charge, has_charge/2)
|
|
|
|
|
|
from sqlalchemy import func, and_
|
|
|
|
# Step 1: Find customer_ids that appear more than once
|
|
duplicate_customer_ids = db.session.query(
|
|
Payments.Stripe_Customer_ID
|
|
).group_by(
|
|
Payments.Stripe_Customer_ID
|
|
).having(
|
|
func.count(Payments.Stripe_Customer_ID) > 1
|
|
).all()
|
|
|
|
# Step 2: Find customer_ids that already have any refunded payments
|
|
customers_with_refunds = db.session.query(
|
|
Payments.Stripe_Customer_ID
|
|
).filter(
|
|
and_(
|
|
Payments.Stripe_Customer_ID.in_([row[0] for row in duplicate_customer_ids]),
|
|
Payments.Refund == True
|
|
)
|
|
).distinct().all()
|
|
|
|
# Step 3: Get customer_ids to process (duplicates minus those with existing refunds)
|
|
customers_to_refund = [
|
|
customer_id for customer_id in [row[0] for row in duplicate_customer_ids]
|
|
if customer_id not in [row[0] for row in customers_with_refunds]
|
|
]
|
|
|
|
print(f"Total duplicate customers: {len(duplicate_customer_ids)}")
|
|
print(f"Customers already with refunds: {len(customers_with_refunds)}")
|
|
print(f"Customers eligible for refund: {len(customers_to_refund)}")
|
|
|
|
# Step 4: Get the first record for each eligible customer
|
|
if customers_to_refund:
|
|
ranked_duplicates = db.session.query(
|
|
Payments.id,
|
|
func.row_number().over(
|
|
partition_by=Payments.Stripe_Customer_ID,
|
|
order_by=Payments.id
|
|
).label('rn')
|
|
).filter(
|
|
Payments.Stripe_Customer_ID.in_(customers_to_refund)
|
|
).subquery()
|
|
|
|
# Get only the first record (rn = 1) for each customer_id
|
|
first_duplicates = db.session.query(Payments).join(
|
|
ranked_duplicates, Payments.id == ranked_duplicates.c.id
|
|
).filter(ranked_duplicates.c.rn == 1).order_by(Payments.Stripe_Customer_ID).all()
|
|
else:
|
|
first_duplicates = []
|
|
|
|
# Now process refunds safely
|
|
results = []
|
|
for payment in first_duplicates:
|
|
if payment.Stripe_Charge_ID:
|
|
print(f"Safe to refund - Payment ID: {payment.id}, Customer: {payment.Stripe_Customer_ID}, Charge: {payment.Stripe_Charge_ID}")
|
|
result = issue_refund_for_payment(
|
|
payment,
|
|
reason='duplicate'
|
|
)
|
|
|
|
results.append(result)
|
|
|
|
if result['success']:
|
|
print(f"✓ Refund successful: {result['refund_id']}")
|
|
else:
|
|
print(f"✗ Refund failed: {result['error']}")
|
|
else:
|
|
print(f"Skipping Payment ID {payment.id} - No Stripe Charge ID")
|
|
|
|
print("\n\n\n")
|
|
print("="*60)
|
|
print("\n RESULTS\n\n")
|
|
print(json.dumps(results,indent=2))
|