def ifttt_bunq_payment(internal, draft): """ Execute a draft, internal or external payment """ config = bunq.retrieve_config() data = request.get_json() print("[action_payment] input: {}".format(json.dumps(data))) errmsg = None if not internal and not draft and not util.get_external_payment_enabled(): errmsg = "external payments disabled" if "actionFields" not in data: errmsg = "missing actionFields" if errmsg: print("[action_payment] ERROR: " + errmsg) return json.dumps({"errors": [{"status": "SKIP", "message": errmsg}]})\ , 400 # get the payment message fields = data["actionFields"] msg = create_payment_message(internal, fields, config) if "errors" in msg or "data" in msg: # error or test payment return json.dumps(msg), 400 if "errors" in msg else 200 # find the source account id source_accid, enabled = check_source_account(internal, draft, config, fields["source_account"]) if source_accid is None: errmsg = "unknown source account: " + fields["source_account"] if not enabled: errmsg = "Payment type not enabled for account: "+\ fields["source_account"] if errmsg: print("[action_payment] ERROR: " + errmsg) return json.dumps({"errors": [{"status": "SKIP", "message": errmsg}]})\ , 400 # execute the payment if draft: msg = {"number_of_required_accepts": 1, "entries": [msg]} result = bunq.post( "v1/user/{}/monetary-account/{}/draft-payment".format( config["user_id"], source_accid), msg) else: result = bunq.post( "v1/user/{}/monetary-account/{}/payment".format( config["user_id"], source_accid), msg) print(result) if "Error" in result: return json.dumps({ "errors": [{ "status": "SKIP", "message": result["Error"][0]["error_description"] }] }), 400 return json.dumps( {"data": [{ "id": str(result["Response"][0]["Id"]["id"]) }]})
def put_callbacks(user_id, account_id, new_notifications): data = { "notification_filters": new_notifications } method = (f"v1/user/{user_id}/monetary-account/{account_id}/" + "notification-filter-url") bunq.post(method, data)
def transfer(row, source_name, target_name, amount, keep, description): accounts = collect_accounts(row) source = find_account(accounts, source_name) if not source: return {"error": f"No account matches source {source_name}"} target = find_account(accounts, target_name) if not target: return {"error": f"No account matches target {target_name}"} source_amount = Decimal(source["value"]) if amount == "all": amount_dec = source_amount if amount_dec <= 0: return {"error": f"There is no money in the source account"} else: amount_dec = Decimal(amount) if source_amount - amount_dec < keep: amount_dec = source_amount - keep if amount_dec <= 0: return {"error": f"Less than 'keep' money in source account"} if Decimal(source["value"]) < amount_dec: return {"error": f"There is not enough money in the source account"} # Move balance to target account method = (f"v1/user/{source['user_id']}/monetary-account/" + f"{source['account_id']}/payment") data = { "amount": { "value": str(amount_dec), "currency": source["currency"] }, "counterparty_alias": { "type": "IBAN", "value": target["iban"], "name": target["name"] }, "description": description } bunq.post(row, method, data) return { "success": "success", "message": f"Transferred {amount_dec} {source['currency']} " + f"from {source['iban']} to {target['iban']}" }
def request_inquiry(): """ Execute a request inquiry action """ data = request.get_json() print("[request_inquiry] input: {}".format(json.dumps(data))) errmsg = None if "actionFields" not in data: errmsg = "missing actionFields" else: fields = data["actionFields"] expected_fields = ["amount", "account", "phone_email_iban"] for field in expected_fields: if field not in fields: errmsg = "missing field: " + field if errmsg: print("[request_inquiry] ERROR: " + errmsg) return json.dumps({"errors": [{"status": "SKIP", "message": errmsg}]})\ , 400 fields["account"] = fields["account"].replace(" ", "") # the account NL42BUNQ0123456789 is used for test payments if fields["account"] == "NL42BUNQ0123456789": return json.dumps({"data": [{"id": uuid.uuid4().hex}]}) accountid = None for acc in util.get_bunq_accounts("PaymentRequest"): if acc["iban"] == fields["account"]: accountid = acc["id"] if accountid is None: errmsg = "unknown account: " + fields["account"] print("[request_inquiry] ERROR: " + errmsg) return json.dumps({"errors": [{"status": "SKIP", "message": errmsg}]})\ , 400 # check amount try: amount = float(fields["amount"]) except ValueError: amount = -1 if amount <= 0: errmsg = "only positive amounts allowed: " + fields["amount"] print("[action_payment] ERROR: " + errmsg) return {"errors": [{"status": "SKIP", "message": errmsg}]} # check phone or email bmvalue = fields["phone_email_iban"].replace(" ", "") if "@" in bmvalue: bmtype = "EMAIL" elif bmvalue[:1] == "+" and bmvalue[1:].isdecimal(): bmtype = "PHONE_NUMBER" elif bmvalue[:2].isalpha() and bmvalue[2:4].isdecimal(): bmtype = "IBAN" else: errmsg = "Unrecognized as email, phone or iban: " + bmvalue print("[request_inquiry] ERROR: " + errmsg) return json.dumps({"errors": [{"status": "SKIP", "message": errmsg}]})\ , 400 description = fields["description"] if "description" in fields else "" msg = { "amount_inquired": { "value": "{:.2f}".format(amount), "currency": "EUR", }, "counterparty_alias": { "type": bmtype, "name": bmvalue, "value": bmvalue }, "description": description, "allow_bunqme": True, } print(json.dumps(msg)) config = bunq.retrieve_config() data = bunq.post("v1/user/{}/monetary-account/{}/request-inquiry".format(\ config["user_id"], accountid), msg, config) print(data) if "Error" in data: return json.dumps({ "errors": [{ "status": "SKIP", "message": data["Error"][0]["error_description"] }] }), 400 return json.dumps({"data": [{"id": uuid.uuid4().hex}]})
if not source: print(f"No account matches source {args.source_name}") sys.exit(1) target = find_account(accounts, args.target_name) if not target: print(f"No account matches target {args.target_name}") sys.exit(1) if Decimal(source["value"]) <= 0: print("There is no money in the source account") sys.exit(1) # Move balance to target account print(f"Sending {source['value']} {source['currency']} from " + f"{source['iban']} to {target['iban']}...") method = (f"v1/user/{source['user_id']}/monetary-account/" + f"{source['account_id']}/payment") data = { "amount": { "value": source["value"], "currency": source["currency"] }, "counterparty_alias": { "type": "IBAN", "value": target["iban"], "name": target["name"] }, "description": "Flushing account" } bunq.post(method, data)
removed_notification = True else: new_notifications.append({ "category": nf["category"], "notification_target": nf["notification_target"] }) if not removed_notification: print("Adding callback...") new_notifications.append({ "category": args.toggle_category, "notification_target": args.toggle_url, }) return new_notifications bunq_user_id = bunq_api.get_user_id(args.bunq_user_name) if args.bunq_account_name: bunq_account_id = bunq_api.get_account_id(bunq_user_id, args.bunq_account_name) method = (f"v1/user/{bunq_user_id}/monetary-account/" f"{bunq_account_id}/notification-filter-url") old_nfs = bunq.get(method) new_nfs = update_notifications(old_nfs) bunq.post(method, {"notification_filters": new_nfs}) else: method = f"v1/user/{bunq_user_id}/notification-filter-url" old_nfs = bunq.get(method) new_nfs = update_notifications(old_nfs) bunq.post(method, {"notification_filters": new_nfs})
def target_balance_internal(): """ Execute a target balance internal action """ data = request.get_json() print("[target_balance_internal] input: {}".format(json.dumps(data))) if "actionFields" not in data: errmsg = "missing actionFields" print("[target_balance_internal] ERROR: " + errmsg) return json.dumps({"errors": [{"status": "SKIP", "message": errmsg}]})\ , 400 fields = data["actionFields"] errmsg = check_fields(True, fields) if errmsg: print("[target_balance_internal] ERROR: " + errmsg) return json.dumps({"errors": [{"status": "SKIP", "message": errmsg}]})\ , 400 # the account NL42BUNQ0123456789 is used for test payments if fields["account"] == "NL42BUNQ0123456789": return json.dumps({"data": [{"id": uuid.uuid4().hex}]}) # retrieve balance config = bunq.retrieve_config() if fields["payment_type"] == "DIRECT": balance = get_balance(config, fields["account"], fields["other_account"]) if isinstance(balance, tuple): balance, balance2 = balance transfer_amount = fields["amount"] - balance if transfer_amount > balance2: transfer_amount = balance2 else: balance = get_balance(config, fields["account"]) if isinstance(balance, float): transfer_amount = fields["amount"] - balance if isinstance(balance, str): errmsg = balance print("[target_balance_internal] ERROR: " + errmsg) return json.dumps({"errors": [{"status": "SKIP", "message": errmsg}]})\ , 400 # construct payment message if "{:.2f}".format(fields["amount"]) == "0.00": errmsg = "No transfer needed, balance already ok" print("[target_balance_internal] ERROR: " + errmsg) return json.dumps({"errors": [{"status": "SKIP", "message": errmsg}]})\ , 400 if transfer_amount > 0 and "top up" in fields["direction"]: paymentmsg = { "amount": { "value": "{:.2f}".format(transfer_amount), "currency": "EUR" }, "counterparty_alias": { "type": "IBAN", "value": fields["account"], "name": "x" }, "description": fields["description"] } account = fields["other_account"] elif transfer_amount < 0 and "skim" in fields["direction"]: paymentmsg = { "amount": { "value": "{:.2f}".format(-transfer_amount), "currency": "EUR" }, "counterparty_alias": { "type": "IBAN", "value": fields["other_account"], "name": "x" }, "description": fields["description"] } account = fields["account"] else: errmsg = "No transfer needed, balance already ok" print("[target_balance_internal] ERROR: " + errmsg) return json.dumps({"errors": [{"status": "SKIP", "message": errmsg}]})\ , 400 print(paymentmsg) # get id and check permissions if fields["payment_type"] == "DIRECT": accid, enabled = payment.check_source_account(True, False, config, account) else: accid, enabled = payment.check_source_account(False, True, config, account) if accid is None: errmsg = "unknown account: " + account if not enabled: errmsg = "Payment type not enabled for account: " + account if errmsg: print("[target_balance_internal] ERROR: " + errmsg) return json.dumps({"errors": [{"status": "SKIP", "message": errmsg}]})\ , 400 # execute the payment if fields["payment_type"] == "DIRECT": result = bunq.post( "v1/user/{}/monetary-account/{}/payment".format( config["user_id"], accid), paymentmsg) else: paymentmsg = {"number_of_required_accepts": 1, "entries": [paymentmsg]} result = bunq.post( "v1/user/{}/monetary-account/{}/draft-payment".format( config["user_id"], accid), paymentmsg) print(result) if "Error" in result: return json.dumps({ "errors": [{ "status": "SKIP", "message": result["Error"][0]["error_description"] }] }), 400 return json.dumps( {"data": [{ "id": str(result["Response"][0]["Id"]["id"]) }]})
def target_balance_external(): """ Execute a target balance external action """ data = request.get_json() print("[target_balance_external] input: {}".format(json.dumps(data))) if "actionFields" not in data: errmsg = "missing actionFields" print("[target_balance_external] ERROR: " + errmsg) return json.dumps({"errors": [{"status": "SKIP", "message": errmsg}]})\ , 400 fields = data["actionFields"] errmsg = check_fields(False, fields) if errmsg: print("[target_balance_external] ERROR: " + errmsg) return json.dumps({"errors": [{"status": "SKIP", "message": errmsg}]})\ , 400 # the account NL42BUNQ0123456789 is used for test payments if fields["account"] == "NL42BUNQ0123456789": return json.dumps({"data": [{"id": uuid.uuid4().hex}]}) # retrieve balance config = bunq.retrieve_config() balance = get_balance(config, fields["account"]) if isinstance(balance, str): errmsg = balance print("[target_balance_external] ERROR: " + errmsg) return json.dumps({"errors": [{"status": "SKIP", "message": errmsg}]})\ , 400 transfer_amount = fields["amount"] - balance # check for zero transfer if "{:.2f}".format(fields["amount"]) == "0.00": errmsg = "No transfer needed, balance already ok" print("[target_balance_external] ERROR: " + errmsg) return json.dumps({"errors": [{"status": "SKIP", "message": errmsg}]})\ , 400 # get account id and check permission if transfer_amount > 0: accid = None for acc in config["accounts"]: if acc["iban"] == fields["account"]: accid = acc["id"] enabled = False if "permissions" in config: if fields["account"] in config["permissions"]: if "PaymentRequest" in config["permissions"]\ [fields["account"]]: enabled = config["permissions"][fields["account"]]\ ["PaymentRequest"] else: accid, enabled = payment.check_source_account(False, True, config, fields["account"]) if accid is None: errmsg = "unknown account: " + fields["account"] if not enabled: errmsg = "Not permitted for account: " + fields["account"] if errmsg: print("[target_balance_external] ERROR: " + errmsg) return json.dumps({"errors": [{"status": "SKIP", "message": errmsg}]})\ , 400 # send request / execute payment if transfer_amount > 0 and "top up" in fields["direction"]: bmvalue = fields["request_phone_email_iban"].replace(" ", "") if "@" in bmvalue: bmtype = "EMAIL" elif bmvalue[:1] == "+" and bmvalue[1:].isdecimal(): bmtype = "PHONE_NUMBER" elif bmvalue[:2].isalpha() and bmvalue[2:4].isdecimal(): bmtype = "IBAN" else: errmsg = "Unrecognized as email, phone or iban: " + bmvalue print("[request_inquiry] ERROR: " + errmsg) return json.dumps({"errors": [{"status": "SKIP", "message":\ errmsg}]}), 400 msg = { "amount_inquired": { "value": "{:.2f}".format(transfer_amount), "currency": "EUR", }, "counterparty_alias": { "type": bmtype, "name": bmvalue, "value": bmvalue }, "description": fields["request_description"], "allow_bunqme": True, } print(json.dumps(msg)) config = bunq.retrieve_config() result = bunq.post("v1/user/{}/monetary-account/{}/request-inquiry"\ .format(config["user_id"], accid), msg, config) elif transfer_amount < 0 and "skim" in fields["direction"]: paymentmsg = { "amount": { "value": "{:.2f}".format(-transfer_amount), "currency": "EUR" }, "counterparty_alias": { "type": "IBAN", "value": fields["payment_account"], "name": fields["payment_name"] }, "description": fields["payment_description"] } print(paymentmsg) paymentmsg = {"number_of_required_accepts": 1, "entries": [paymentmsg]} result = bunq.post( "v1/user/{}/monetary-account/{}/draft-payment".format( config["user_id"], accid), paymentmsg) else: errmsg = "No transfer needed, balance already ok" print("[target_balance_external] ERROR: " + errmsg) return json.dumps({"errors": [{"status": "SKIP", "message": errmsg}]})\ , 400 print(result) if "Error" in result: return json.dumps({ "errors": [{ "status": "SKIP", "message": result["Error"][0]["error_description"] }] }), 400 return json.dumps( {"data": [{ "id": str(result["Response"][0]["Id"]["id"]) }]})