Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add donors scraping CGI scripts #442

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
191 changes: 191 additions & 0 deletions cgi-bin/stripe_donor_logger.cgi
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

""" stripe_donor_logger.cgi

This scripts is to be run as a cgi on a public server.
By creating a webhook at stripe for the payments to qgis/QGIS this
script will be called, with the commit data as json (sent by striep).

Given this data the script will do the following:
- load the json and extract donationid, donorname and donordate
- log success and errors to /tmp/stripehook.log
- call the QGIS-Website/scripts/commit_donor.sh with the donorname as 1st arg
- that script will:
- git pull --rebase the QGIS-Website source
- call the perl script which adds the donorname to the donors.json file
- and commit this to github

There is a special linux user created for this: stripe@qgis2.

"""

import sys
import os
import io
import json
import time
from subprocess import Popen, PIPE


def log(msg):
with open('/tmp/stripehook.log', 'a', encoding="utf-8") as f:
f.write('{}\n'.format(msg))

def test():
charge_success = '''{
"object": {
"id": "ch_1CUyLrBBMqjyXePiy9wBoSW1",
"object": "charge",
"amount": 200,
"amount_refunded": 0,
"application": null,
"application_fee": null,
"balance_transaction": "txn_1CUx4ewrr3DBBMqjyXePig0uuohxp",
"captured": true,
"created": 1527082306,
"currency": "eur",
"customer": "cus_Cumd7sadfdegPGPUoVU",
"description": "QGIS Donation",
"destination": null,
"dispute": null,
"failure_code": null,
"failure_message": null,
"fraud_details": {
},
"invoice": null,
"livemode": true,
"metadata": {
"email": "[email protected]",
"donorname": "אבלין李 克勤"
},
"on_behalf_of": null,
"order": null,
"outcome": {
"network_status": "approved_by_network",
"reason": null,
"risk_level": "normal",
"seller_message": "Payment complete.",
"type": "authorized"
},
"paid": true,
"receipt_email": null,
"receipt_number": null,
"refunded": false,
"refunds": {
"object": "list",
"data": [
],
"has_more": false,
"total_count": 0,
"url": "/v1/charges/ch_1CUx4dafaeeafABBMqjyXePi7Lu5EhcI/refunds"
},
"review": null,
"shipping": null,
"source": {
"id": "card_1CUx3tBBMqjydafdXePiM6yo4onz",
"object": "card",
"address_city": null,
"address_country": null,
"address_line1": null,
"address_line1_check": null,
"address_line2": null,
"address_state": null,
"address_zip": null,
"address_zip_check": null,
"brand": "Visa",
"country": "NL",
"customer": "cus_Cumddfa7sgPGPUoVU",
"cvc_check": "pass",
"dynamic_last4": null,
"exp_month": 12,
"exp_year": 1920,
"fingerprint": "MzssdxRNkG8GPuXIaP2",
"funding": "credit",
"last4": "4242",
"metadata": {
},
"name": null,
"tokenization_method": null
},
"source_transfer": null,
"statement_descriptor": null,
"status": "succeeded",
"transfer_group": null
},
"previous_attributes": null
}'''
return charge_success

def sent_content(status, msg):
# http://www.restpatterns.org/HTTP_Status_Codes
print('Status: {}'.format(status))
print('Content-Type: text/plain')
print('')
print('{}'.format(msg))


# THE ACTUAL CGI

# TO TEST: make method GET and take one of the jsons from test()
method = os.environ["REQUEST_METHOD"]

# TODO: check the 'secret' == if the Stripe-Signature contains our SECRET
SECRET='****'

try:
# TO TEST: make method GET and take one of the jsons from test()
if method == "GET":
rawdata = test()
elif method == "POST":
# duh, sys.stdin.read guesses the encoding... and in case of french guesses ascii: wrong!
# https://stackoverflow.com/questions/16549332/python-3-how-to-specify-stdin-encoding
# so instead of reading directly from sys.stdin we read via iowrapper:
#rawdata = sys.stdin.read()
content_length = int(os.environ.get('CONTENT_LENGTH', 0))
log(str(content_length) + ' content_length')
if content_length > 0:
# input_stream = io.TextIOWrapper(sys.stdin.buffer, encoding='utf-8')
# # Limit the size of the data read to the content_length
rawdata = sys.stdin.buffer.read(content_length)
rawdata = rawdata.decode('utf-8')
else:
rawdata = ''
else:
sent_content(400, 'Illegal request')
sys.exit(400)

data = json.loads(rawdata)
# sometimes not only the charge object is sent, but also the evt object surrounding it:
if data['object']=='event':
log('event received instead of charge...')
data = data['data']
donationid = data['object']['id']
donationtime = time.asctime(time.gmtime(data['object']['created']))
donorname = data['object']['metadata'].get('donorname')
if donorname is None or len(donorname)<1:
# we promised to only publish the donorname if it was given!
pass
else:
# casting to Title case
donorname = donorname.title()
# have to sent bytes utf-8 encoded to command??
#command = ['scripts/commit_donor.sh', donorname]
command = ['scripts/commit_donor.sh', donorname]
cwd = os.getcwd()
result = Popen(command, cwd=cwd, stdout=PIPE, stderr=PIPE)
(status, error) = result.communicate()
if result.poll() == 0:
log('Successfully add+commit of donation {} dd {} (UTC) by "{}" see https://dashboard.stripe.com/payments/{}'.format(donationid, donationtime, donorname, donationid))
else:
log('ERROR add+commit of donation {} dd {} (UTC) by "{}" see https://dashboard.stripe.com/payments/{}'.format(donationid, donationtime, donorname, donationid))
log(error)
sent_content(500, 'Some unexpected error occurred. Error text was: {}'.format(error))
sys.exit(500)
# all fine: sent 200 back to Stripe
sent_content(200, 'Ok')

except Exception as error:
log('ERROR in script: {}'.format(error))
sent_content(500, 'Some unexpected error occurred. Error text was: {}'.format(error))
sys.exit(500)
79 changes: 79 additions & 0 deletions scripts/add_donor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
This script should be run from the root repo directory (../)

$ python scripts/add_donor.py "John Doe"

It will update the json file (donors.json)
ordered alphabetically by the first name.

All names should start with a capital letter,
names where all characters are capitalized
should be converted to lower case letters
(except for the first character of the name).

Trailing dots or commas should be removed.
If a name is already in the list, it should
not be added again.

"""

import json
import re
import sys


def log(msg):
with open('/tmp/stripehook.log', 'a', encoding="utf-8") as f:
f.write('{}\n'.format(msg))

# Function to clean and format names
def format_name(name):
# Remove trailing dots or commas
name = re.sub(r'[.,]+$', '', name.strip())

# Ensure that the first letter of each word is capitalized
# Convert to title case (first letter uppercase, others lowercase)
name = name.title()
return name


# Function to update donors list with the new name
def update_donors(new_donor_name, donors_json_file):
# Read the existing donors from the JSON file
try:
with open(donors_json_file, 'r') as f:
data = json.load(f)
donors = data.get("donors", [])
except FileNotFoundError:
donors = []
data = {"donors": donors}

# Clean and format the new donor's name
new_donor = format_name(new_donor_name)

# Add the new donor to the list if not already present
if new_donor not in donors:
donors.append(new_donor)

# Sort donors alphabetically by first name
donors.sort()

# Write the updated donors list back to the JSON file
with open(donors_json_file, 'w') as f:
json.dump(data, f, ensure_ascii=False, indent=4)


if __name__ == "__main__":
if len(sys.argv) != 2:
print("Usage: python add_donor.py \"John Doe\"")
sys.exit(1)
new_donor_name = sys.argv[1] # Name passed as argument
log(f"Adding new donor: {new_donor_name}")
donors_json_file = 'data/donors.json' # Path to the donors.json file

# Update the donors list with the new name
update_donors(new_donor_name, donors_json_file)
print(f"Donor '{new_donor_name}' has been added and the list updated.")
45 changes: 45 additions & 0 deletions scripts/commit_donor.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#!/usr/bin/env bash
#!encoding: UTF-8

# Set the scripts directory as the current working directory
# cd "${0%/*}"

# Define log file
LOG_FILE="/tmp/stripehook.log"

# Log function to append messages to the log file
log() {
echo "$(date +"%Y-%m-%d %H:%M:%S") - $1" >> "$LOG_FILE"
}

# Check if the script is already running
if [ -f running ]; then
log "$0 still running"
exit 1
fi
touch running

# Log and execute git pull
log "Executing git pull"
git pull --rebase >> "$LOG_FILE" 2>&1

# Log and execute add_donor.py script
log "Adding donor: $1"
./scripts/add_donor.py "$1" >> "$LOG_FILE" 2>&1

# Log and execute git commit
log "Committing donor $1"
git commit -a -m "Donor $1 added" >> "$LOG_FILE" 2>&1

# Log and execute git push
log "Pushing changes to origin main"
git push origin main >> "$LOG_FILE" 2>&1

# Cleanup
rm running
log "Script completed successfully"

# Deploy the changes
# make deploy

exit 0
Loading