-
Notifications
You must be signed in to change notification settings - Fork 0
/
payment.py
194 lines (171 loc) · 8.37 KB
/
payment.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
#!/usr/bin/env python3
"""
Copyright 2023 Paul Willworth <[email protected]>
"""
import time
from datetime import timezone, datetime
from web3 import Web3
from web3.logs import STRICT, IGNORE, DISCARD, WARN
import decimal
import db
import settings
#
PAYMENT_ADDRESS = '0xF4A6a8d2c9dDe9Fecd6020851c6212b43C20d97E'
PAYMENT_TOKENS = {
'dfkchain': ['0xCCb93dABD71c8Dad03Fc4CE5559dC3D89F67a260','0x04b9dA42306B023f3572e106B11D82aAd9D32EBb','0x3AD9DFE640E1A9Cc1D9B0948620820D975c3803a'],
'klaytn': ['0x19Aac5f612f524B754CA7e7c41cbFa2E981A4432','0x30C103f8f5A3A732DFe2dCE1Cc9446f545527b43','0xB3F5867E277798b50ba7A71C0b24FDcA03045eDF']
}
TOKEN_SUB_VALUES = {
'0x19Aac5f612f524B754CA7e7c41cbFa2E981A4432': 0.15, #klay
'0xCCb93dABD71c8Dad03Fc4CE5559dC3D89F67a260': 0.25, #dfkJewel
'0x30C103f8f5A3A732DFe2dCE1Cc9446f545527b43': 0.25, #klayJewel
'0x04b9dA42306B023f3572e106B11D82aAd9D32EBb': 0.02, #crystal
'0xB3F5867E277798b50ba7A71C0b24FDcA03045eDF': 0.01, #jabe
'0x3AD9DFE640E1A9Cc1D9B0948620820D975c3803a': 1.00 #dfkUSDC
}
def extractTokenResults(w3, account, receipt, network):
contract = w3.eth.contract(address='0x72Cb10C6bfA5624dD07Ef608027E366bd690048F', abi=settings.ERC20_ABI)
decoded_logs = contract.events.Transfer().process_receipt(receipt, errors=DISCARD)
transfers = []
for log in decoded_logs:
if log['args']['from'] == account:
otherAddress = log['args']['to']
elif log['args']['to'] == account:
otherAddress = log['args']['from']
else:
continue
tokenValue = valueFromWei(log['args']['value'], log['address'], network)
if tokenValue > 0:
r = [otherAddress, log['address'], tokenValue]
transfers.append(r)
return transfers
# Simple way to determine conversion, maybe change to lookup on chain later
def valueFromWei(amount, token, network):
#w3.fromWei doesn't seem to have an 8 decimal option for BTC
if token in ['0x3095c7557bCb296ccc6e363DE01b760bA031F2d9', '0xdc54046c0451f9269FEe1840aeC808D36015697d','0x7516EB8B8Edfa420f540a162335eACF3ea05a247','0x16D0e1fBD024c600Ca0380A4C5D57Ee7a2eCBf9c']:
return decimal.Decimal(amount) / decimal.Decimal(100000000)
elif token in ['0x985458E523dB3d53125813eD68c274899e9DfAb4','0x3C2B8Be99c50593081EAA2A724F0B8285F5aba8f','0xA7D7079b0FEaD91F3e65f86E8915Cb59c1a4C664','0xceE8FAF64bB97a73bb51E115Aa89C17FfA8dD167','0x6270B58BE569a7c0b8f47594F191631Ae5b2C86C']: # 1USDC/1USDT
weiConvert = 'mwei'
else:
weiConvert = 'ether'
return Web3.from_wei(amount, weiConvert)
def getSubscriptionTime(token, amount):
tokenRatio = TOKEN_SUB_VALUES[token]
tokenValue = amount * decimal.Decimal(tokenRatio)
if tokenValue < 0.5:
return 0
if tokenValue > 10:
return tokenValue * 86400 * 6
return tokenValue * 86400 * 4
def addPayment(account, txHash, token, amount, network, purchaseTime):
result = 0
con = db.aConn()
with con.cursor() as cur:
# ensure tx not already credited
cur.execute("SELECT generatedTimestamp FROM payments WHERE txHash=%s AND network=%s", (txHash, network))
row = cur.fetchone()
if row != None and row[0] != None:
result = -1
else:
# calculate time to add and new expiration
generateTime = int(datetime.now(timezone.utc).timestamp())
cur.execute("SELECT expiresTimestamp FROM members WHERE account = %s", (account,))
rowu = cur.fetchone()
previousExpires = 0
if rowu != None:
if rowu[0] == None:
previousExpires = 0
else:
previousExpires = rowu[0]
# credit from current time if expired in past or from future if still active
newExpires = max(generateTime, previousExpires) + int(purchaseTime)
cur.execute("INSERT INTO payments (account, generatedTimestamp, txHash, token, amount, previousExpires, newExpires, network) VALUES (%s, (now() at time zone 'utc'), %s, %s, %s, %s, %s, %s)", (account, txHash, token, amount, previousExpires, newExpires, network))
result = cur.rowcount
# member record updated if it existed otherwise payments can be picked up on member registration
if rowu != None:
cur.execute("UPDATE members SET expiresTimestamp=%s WHERE account=%s", (newExpires, account))
con.close()
return result
def validatePayment(network, account, txHash):
failure = False
response = ''
txToken = ''
tokenAmount = 0
if not Web3.is_address(account):
response = { "error" : "Error: The account you provided is not a vaild wallet address: {0}.".format(account) }
failure = True
else:
account = Web3.to_checksum_address(account)
# Connect to right network that txs are for
if network == 'klaytn':
w3 = Web3(Web3.HTTPProvider(settings.klaytn_web3))
elif network == 'dfkchain':
w3 = Web3(Web3.HTTPProvider(settings.dfk_web3))
else:
response = { "error" : "Error: Invalid network {0} only dfkchain and klaytn supported.".format(network) }
failure = True
if not w3.is_connected():
response = { "error" : "Error: Critical w3 connection failure for, {0}, please try again later.".format(network) }
failure = True
# Initial Tx validation
if failure == False:
# Pause a little to ensure tx is returned from this method as it is sometimes not found right after receipt is available
time.sleep(5)
try:
result = w3.eth.get_transaction(txHash)
except Exception as err:
response = ''.join(('{ "error" : "Error: Invalid transaction, additional info: ', str(err), '." }'))
failure = True
# Tx results Validation
if failure == False:
if result['from'] != account:
response = ''.join(('{ "error" : "Error: That transaction was from: ', result['from'], ', not you." }'))
failure = True
if failure == False:
txValue = Web3.from_wei(result['value'], 'ether')
if txValue > 0 and result['to'] != PAYMENT_ADDRESS:
response = ''.join(('{ "error" : "Error: That transaction went to: ', result['to'], ', not the payment address." }'))
failure = True
if failure == False:
try:
receipt = w3.eth.get_transaction_receipt(txHash)
except Exception as err:
response = ''.join(('{ "error" : "Error: Unable to get transaction details: ', str(err), '." }'))
failure = True
if failure == False and receipt['status'] != 1:
response = ''.join(('{ "error" : "Error: That transaction failed, please try your payment again." }'))
failure = True
# Tx content Validation
if failure == False:
if txValue > 0:
txToken = settings.GAS_TOKENS[network]
tokenAmount = txValue
else:
# also check for any random token trasfers in the wallet
results = extractTokenResults(w3, account, receipt, network)
for xfer in results:
if xfer[0] == PAYMENT_ADDRESS:
txToken = xfer[1]
tokenAmount = xfer[2]
if txToken not in PAYMENT_TOKENS[network] or not tokenAmount > 0:
response = ''.join(('{ "error" : "Error: No valid token transfers were found in that transaction." }'))
failure = True
if failure == False:
purchaseTime = getSubscriptionTime(txToken, tokenAmount)
if purchaseTime > 0:
updateResult = addPayment(account, txHash, txToken, tokenAmount, network, purchaseTime)
if updateResult == 0:
response = { "error": "Payment not posted, please contact site admin to ensure credit." }
elif updateResult == -1:
response = { "error": "Error: that transaction has already been verified, it cannot be used again." }
else:
response = { "updated": "{0} Payment posted".format(updateResult) }
else:
response = { "error": "The value of that transaction is below the minimum amount to add subscription time." }
return response
def main():
# test
result = validatePayment('dfkchain', '0x9D4D95FbE9Fb951D8Ef9A1d8dF70105da7F156F2', '0x7b6a17ccf195855849d1d5c66312c73a43531ac51ae6d3fc00ecc922a37d9a87')
print(result)
if __name__ == "__main__":
main()