def conditions_fulfilled(self): if self.txid is None: return False data = transaction(txid=self.txid) if isinstance( data, dict ) and 'transaction' in data and 'confirmations' in data['transaction']: confirmations = data['transaction']['confirmations'] else: # Something went wrong during retrieval of transaction return False return True if self.confirmations <= confirmations else False
def run(self): LOG.info('Running Spellbook Script: %s' % os.path.splitext(os.path.basename(__file__))[0]) if self.json is not None: if 'payment_request_id' not in self.json: LOG.error( 'Payment request json does not contain the payment_request_id!' ) return if 'txid' not in self.json: LOG.error('Payment request json does not contain the txid!') return payment_request_id = self.json['payment_request_id'] try: payment_request = PaymentRequest( payment_request_id=payment_request_id) except Exception as ex: self.http_response = {'error': str(ex)} return # Note: because this is just an example app, we will ignore the possibility of transaction malleability txid = self.json['txid'] LOG.info('Payment request %s received a transaction: %s' % (payment_request_id, txid)) tx_data = transaction(txid=txid) if 'transaction' not in tx_data: LOG.error('Unable to retrieve transaction %s' % txid) return tx = tx_data['transaction'] transaction_received = False # Note: we are assuming there is only a single output to the payment address for tx_output in tx['outputs']: if tx_output[ 'address'] == payment_request.address and tx_output[ 'value'] == payment_request.amount_btc: LOG.info('Transaction contains valid payment!') transaction_received = True payment_request.txid = txid payment_request.status = 'Transaction received' elif tx_output[ 'address'] == payment_request.address and tx_output[ 'value'] < payment_request.amount_btc: LOG.info('Transaction has less than the requested amount!') payment_request.status = 'Insufficient amount received' elif tx_output[ 'address'] == payment_request.address and tx_output[ 'value'] > payment_request.amount_btc: LOG.info('Transaction has more than the requested amount!') transaction_received = True payment_request.txid = txid payment_request.status = 'Excess amount received' if transaction_received is False: LOG.info('Transaction does not contain a valid payment!') return payment_request.confirmations = tx['confirmations'] if payment_request.confirmations >= 6: payment_request.status = 'Confirmed' payment_request.save() # Send an email to notify we received the transaction action = get_action(action_id='tx_received_email', action_type=ActionType.SENDMAIL) action.mail_subject = 'Transaction received for payment %s' % payment_request.payment_request_id action.mail_recipients = NOTIFICATION_EMAIL action.mail_body_template = os.path.join( 'PaymentProcessor', 'templates', 'TransactionReceived.txt' ) # The spellbook will search for the template in the 'email_templates' and in the 'apps' directory, subdirectories are allowed, just need to specify the full path as shown here action.mail_variables = { 'PAYMENT_REQUEST_ID': payment_request.payment_request_id, 'SELLER_ID': payment_request.seller_id, 'AMOUNT_FIAT': payment_request.amount_fiat, 'CURRENCY': payment_request.currency, 'NOTE': payment_request.note, 'ADDRESS': payment_request.address, 'AMOUNT_BTC': payment_request.amount_btc / 1e8, # amount is in satoshis, display in BTC 'PRICE_BTC': payment_request.price_btc, 'PRICE_TIMESTAMP': datetime.fromtimestamp( payment_request.price_timestamp).strftime( '%Y-%m-%d %H:%M:%S'), 'TXID': payment_request.txid } action.run()
def get_transaction(txid): response.content_type = 'application/json' return transaction(txid)
def run(self): LOG.info('Running Spellbook Script: %s' % os.path.splitext(os.path.basename(__file__))[0]) LOG.info('triggered: %s' % self.triggered) # If the trigger contains data, override the information in self.json if self.data is not None: if self.json is None: self.json = {} for key, value in self.data.items(): self.json[key] = value if self.json is not None: if 'payment_request_id' not in self.json: LOG.error( 'Payment request json does not contain the payment request id!' ) return try: payment_request = PaymentRequest( payment_request_id=self.json['payment_request_id']) except Exception as ex: self.http_response = {'error': str(ex)} return if payment_request.txid is not None: tx_data = transaction(txid=payment_request.txid) tx = tx_data['transaction'] if 'transaction' in tx_data else {} payment_request.confirmations = tx[ 'confirmations'] if 'confirmations' in tx else None else: # Fallback method in case the listener did not pick up the transaction (tx might have happened after listener timed out) balance_data = balance(address=payment_request.address) # If the final balance of the address is equal or more than the requested amount, assume the most recent transaction is the payment transaction # Note: there could be situations where multiple transactions are sent, those situations are not handled in this example app if 'balance' in balance_data and balance_data['balance'][ 'final'] >= payment_request.amount_btc: LOG.info( 'Current balance of %s: %s' % (payment_request.address, balance_data['balance'])) transactions_data = transactions( address=payment_request.address) if 'transactions' in transactions_data: tx = transactions_data['transactions'][-1] payment_request.txid = tx['txid'] payment_request.confirmations = tx['confirmations'] if payment_request.confirmations >= 6: payment_request.status = 'Confirmed' elif 1 <= payment_request.confirmations < 6: payment_request.status = '%s of 6 confirmations' % payment_request.confirmations payment_request.save() LOG.info('Retrieving status of payment request: %s' % payment_request.payment_request_id) LOG.info('Seller id: %s' % payment_request.seller_id) LOG.info('Fiat Amount: %s %s' % (payment_request.amount_fiat, payment_request.currency)) LOG.info('BTC Amount: %s (price %s %s/BTC @ %s)' % (payment_request.amount_btc, payment_request.price_btc, payment_request.currency, datetime.fromtimestamp(payment_request.price_timestamp ).strftime('%Y-%m-%d %H:%M:%S'))) LOG.info('Note: %s' % payment_request.note) LOG.info('Address: %s' % payment_request.address) LOG.info('Status: %s' % payment_request.status) LOG.info('Txid: %s' % payment_request.txid) LOG.info('Confirmations: %s' % payment_request.confirmations) self.http_response = payment_request.json_encodable() if payment_request.status == 'Confirmed': # Send an email to notify the payment is confirmed action = get_action(action_id='tx_received_email', action_type=ActionType.SENDMAIL) action.mail_subject = 'Payment request %s is confirmed' % payment_request.payment_request_id action.mail_recipients = NOTIFICATION_EMAIL action.mail_body_template = os.path.join( 'PaymentProcessor', 'templates', 'PaymentConfirmed.txt' ) # The spellbook will search for the template in the 'email_templates' and in the 'apps' directory, subdirectories are allowed, just need to specify the full path as shown here action.mail_variables = { 'PAYMENT_REQUEST_ID': payment_request.payment_request_id, 'SELLER_ID': payment_request.seller_id, 'AMOUNT_FIAT': payment_request.amount_fiat, 'CURRENCY': payment_request.currency, 'NOTE': payment_request.note, 'ADDRESS': payment_request.address, 'AMOUNT_BTC': payment_request.amount_btc / 1e8, # amount is in satoshis, display in BTC 'PRICE_BTC': payment_request.price_btc, 'PRICE_TIMESTAMP': datetime.fromtimestamp( payment_request.price_timestamp).strftime( '%Y-%m-%d %H:%M:%S'), 'TXID': payment_request.txid } action.run() # Now that the payment is complete and confirmation email is sent, we can delete the trigger by setting # the self_destruct time to something in the past, this will delete the trigger next time check_triggers happens # We can not delete it right now because it is still in use by the spellbookserver trigger = get_trigger( trigger_id=payment_request.payment_request_id, trigger_type=TriggerType.BALANCE) trigger.self_destruct = int(time.time()) - 1 trigger.save()