def get_transactions(row): result = [] users = bunq.get(row, 'v1/user') for u in users: for k, v in u.items(): result += process_user(row, v['id']) return result
def get_user_id(user_name): for u in bunq.get('v1/user'): for k, v in u.items(): if (v["display_name"].casefold() == user_name.casefold() or str(v["id"]) == user_name): return str(v["id"]) raise Exception("BUNQ user '{0}' not found".format(user_name))
def get_account_id(user_id, account_name): reply = bunq.get('v1/user/' + user_id + '/monetary-account-bank') for a in [a["MonetaryAccountBank"] for a in reply]: if (a["description"].casefold() == account_name.casefold() or str(a["id"]) == account_name): return str(a["id"]) raise Exception("BUNQ account '{0}' not found".format(account_name))
def process_user(self, user_id): method = 'v1/user/{0}/monetary-account'.format(user_id) for a in bunq.get(self.row, method): if self.time_exceeded() or self.age_exceeded(): return for k, v in a.items(): yield from self.process_account(user_id, v["id"])
def get_transactions(user_id, account_id): method = ("v1/user/{0}/monetary-account/{1}/payment?count=100".format( user_id, account_id)) payments = bunq.get(method) print("Translating payments...") transactions = [] first_day = None unsorted_payments = [p["Payment"] for p in payments] payments = sorted(unsorted_payments, key=lambda p: p["created"]) for p in payments: if p["amount"]["currency"] != "EUR": raise Exception("Non-euro payment: " + p["amount"]["currency"]) date = p["created"][:10] if not first_day or date < first_day: first_day = date transactions.append({ "amount": p["amount"]["value"], "date": date, "payee": p["counterparty_alias"]["display_name"], "description": p["description"] }) # For correct duplicate calculation, return only complete days return [t for t in transactions if first_day < t["date"]]
def update_bunq_callback(accurl, cat, value, url_base, url_method): """ Update the bunq callback """ res = bunq.get("v1/user/{}/{}".format(get_bunq_userid(), accurl)) for typ in res["Response"][0]: data = res["Response"][0][typ] filtered = [] if "notification_filters" in data: for filt in data["notification_filters"]: # keep anything not set by us if filt["category"] != cat or \ not filt["notification_target"].endswith(url_method): filtered.append(filt) if value: filtered.append({ "notification_delivery_method": "URL", "notification_target": url_base + url_method, "category": cat }) print("New: ", filtered) res = bunq.put("v1/user/{}/{}".format(get_bunq_userid(), accurl), {"notification_filters": filtered}) if 'Error' in res: print("Result: ", res) return False return True
def process_user(row, user_id): result = "" method = 'v1/user/{0}/monetary-account'.format(user_id) for a in bunq.get(row, method): for k, v in a.items(): result += process_account(row, user_id, v["id"]) return result
def sync(arg1, arg2): bunq_account_id = os.getenv('BUNQ_ACCOUNT_ID') ynab_account_id = os.getenv('YNAB_ACCOUNT_ID') if bunq_account_id is None: method = 'v1/user/{0}/monetary-account'.format(bunq_user_id) for a in bunq.get(method): for k, v in a.items(): bunq_account_id = v["id"] bunq_account_description = v["description"] # Get corresponding YNAB account ID ynab_account_id = get_ynab_account_id(bunq_account_description) if ynab_account_id is not None: sync_bunq_to_ynab(bunq_user_id, bunq_account_id, ynab_budget_id, ynab_account_id) else: print( f"No YNAB account with name {bunq_account_description} found, skipping." ) else: sync_bunq_to_ynab(bunq_user_id, bunq_account_id, ynab_budget_id, ynab_account_id)
def retrieve_and_save_bunq_userid(): """ Retrieve the bunq userid from bunq and save it """ global _BUNQ_USERID result = bunq.get("v1/user") for user in result["Response"]: for typ in user: _BUNQ_USERID = user[typ]["id"] storage.store("config", "bunq_userid", {"value": _BUNQ_USERID})
def print_accounts(userid): method = 'v1/user/{0}/monetary-account'.format(userid) for a in bunq.get(method): for k, v in a.items(): print(" {}".format(k)) print(" {0:28} {1:10,} {2:3} ({3})".format( v["description"], Decimal(v["balance"]["value"]), v["balance"]["currency"], v["id"]))
def get_transactions(row): result = ("amount,currency,created,type,sub_type,description," + "from,from_name,to_iban,to_name\n") users = bunq.get(row, 'v1/user') for u in users: for k, v in u.items(): #result += f"{k} {v['display_name']} {v['id']}" result += process_user(row, v['id']) return result
def get_account_id(user_id, account_name): reply = bunq.get('v1/user/{0}/monetary-account'.format(user_id)) for entry in reply: account_type = next(iter(entry)) account = entry[account_type] if (account["description"].casefold() == account_name.casefold() or str(account["id"]) == account_name): return str(account["id"]) raise Exception("BUNQ account '{0}' not found".format(account_name))
def process_account(self, user_id, account_id): method = ("v1/user/{0}/monetary-account/{1}/payment?count=200".format( user_id, account_id)) payments = bunq.get(self.row, method) yield from self.process_payments(payments) while bunq.has_previous(): if self.time_exceeded() or self.age_exceeded(): return payments = bunq.previous(self.row) yield from self.process_payments(payments)
def update_bunq_accounts(): """ Update the list of bunq accounts for the user """ accounts_local = [] accounts_callback = [] result = bunq.get("v1/user/{}/monetary-account".format(get_bunq_userid())) for res in result["Response"]: for typ in res: acc = res[typ] type_url = _TYPE_TRANSLATION[typ] if acc["status"] == "ACTIVE": iban = None for alias in acc["alias"]: if alias["type"] == "IBAN": iban = alias["value"] name = alias["name"] accinfo = { "iban": iban, "name": name, "type": type_url, "id": acc["id"], "description": acc["description"] } accounts_local.append(accinfo.copy()) accinfo["enableMutation"] = False accinfo["enableRequest"] = False accinfo["callbackMutation"] = False accinfo["callbackRequest"] = False accinfo["callbackOther"] = [] if "notification_filters" in acc: for noti in acc["notification_filters"]: url = noti["notification_target"] if noti["category"] == "MUTATION" and \ noti["notification_target"]\ .endswith("/bunq2ifttt_mutation"): accinfo["enableMutation"] = True accinfo["callbackMutation"] = url elif noti["category"] == "REQUEST" and \ noti["notification_target"]\ .endswith("/bunq2ifttt_request"): accinfo["enableRequest"] = True accinfo["callbackRequest"] = url else: accinfo["callbackOther"].append(\ {"cat": noti["category"], "url": url, "b64url": base64.urlsafe_b64encode(\ url.encode("utf-8")).decode("ascii")}) print(len(accinfo["callbackOther"])) accounts_callback.append(accinfo) process_bunq_accounts_local(accounts_local) if get_bunq_security_mode() == "API key": process_bunq_accounts_callback(accounts_callback)
def set_autosync_callbacks(new_nfs): bunq_account_id = os.getenv('BUNQ_ACCOUNT_ID') url = os.getenv('LAMBDA_CALLBACK_URL') if bunq_account_id is not None: update_callbacks(bunq_account_id, new_nfs) else: method = 'v1/user/{0}/monetary-account'.format(bunq_user_id) for a in bunq.get(method): for k, v in a.items(): bunq_account_id = v["id"] update_callbacks(bunq_account_id, new_nfs)
def process_account(row, user_id, account_id): result = "" method = ("v1/user/{0}/monetary-account/{1}/payment?count=24".format( user_id, account_id)) payments = bunq.get(row, method) for v in [p["Payment"] for p in payments]: result += "{0},{1},{2},{3},{4},{5},{6},{7},{8},{9}\n".format( v["amount"]["value"], v["amount"]["currency"], v["created"][:16], v["type"], v["sub_type"], v["description"], v["alias"]["iban"], v["alias"]["display_name"], v["counterparty_alias"]["iban"], v["counterparty_alias"]["display_name"]) return result
def get_payments(self): self.last_payment = None self.end_time = time.time() + self.time_limit dt = datetime.datetime.now() - datetime.timedelta(weeks=53) self.oldest_payment_dt = dt.strftime("%Y-%m-%d") yield ("amount,currency,created,type,sub_type,description," + "from_iban,from_name,to_iban,to_name\n").encode() users = bunq.get(self.row, 'v1/user') for u in users: if self.time_exceeded(): return for k, v in u.items(): #result += f"{k} {v['display_name']} {v['id']}" yield from self.process_user(v['id'])
def get_bunq_cards(): """ Return the list of bunq cards """ config = bunq.retrieve_config() data = bunq.get("v1/user/{}/card".format(config["user_id"]), config) results = [] for item in data["Response"]: for typ in item: card = item[typ] if card["status"] == "ACTIVE": if card["type"] != "MASTERCARD_VIRTUAL": print(card) results.append({ "label": card["second_line"], "value": str(card["id"]) }) return sorted(results, key=lambda k: k["label"])
def collect_user_accounts(row, user_id): accounts = [] method = f"v1/user/{user_id}/monetary-account" for e in bunq.get(row, method): account_type = next(iter(e)) a = e[account_type] for al in a["alias"]: if al["type"] == "IBAN": accounts.append({ "user_id": user_id, "account_id": a["id"], "description": a["description"], "iban": al["value"], "value": a["balance"]["value"], "currency": a["balance"]["currency"], "name": al["name"] }) return accounts
def process_account(row, user_id, account_id): result = [] method = ("v1/user/{0}/monetary-account/{1}/payment?count=24".format( user_id, account_id)) payments = bunq.get(row, method) for v in [p["Payment"] for p in payments]: result.append({ "amount": v["amount"]["value"], "currency": v["amount"]["currency"], "created": v["created"][:16], "type": v["type"], "sub_type": v["sub_type"], "description": v["description"], "from_iban": v["alias"]["iban"], "from_name": v["alias"]["display_name"], "to_iban": v["counterparty_alias"]["iban"], "to_name": v["counterparty_alias"]["display_name"] }) return result
def get_callbacks(user_id, account_id): method = (f"v1/user/{user_id}/monetary-account/{account_id}/" + "notification-filter-url") return bunq.get(method)
def get_account_type(user_id, account_id): reply = bunq.get('v1/user/{0}/monetary-account/{1}'.format( user_id, account_id)) return next(iter(reply[0]))
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 collect_accounts(row): accounts = [] for u in bunq.get(row, 'v1/user'): for k, v in u.items(): accounts.extend(collect_user_accounts(row, v['id'])) return accounts
help="Show content of JSON messages") parser.add_argument("-vv", action="store_true", help="Show JSON messages and HTTP headers") args = parser.parse_args() log_level = 2 if args.vv else 1 if args.v else 0 bunq.set_log_level(log_level) def print_notification_filter(e): nfs = e["notification_filters"] if not nfs: print(" No callbacks") return for nf in nfs: print(' {0:35} {1:10} {2}'.format( nf["category"], nf["notification_delivery_method"], nf.get("notification_target", "-"))) users = bunq.get("v1/user") for u in users: for k, v in u.items(): print('{0} "{1}":'.format(k, v["display_name"])) print_notification_filter(v) method = 'v1/user/{0}/monetary-account'.format(v["id"]) for a in [a["MonetaryAccountBank"] for a in bunq.get(method)]: print('{} "{}" "{}":'.format(k, v["display_name"], a["description"])) print_notification_filter(a)
def change_card_account(): """ Execute a change card account action """ data = request.get_json() print("[change_card_account] input: {}".format(json.dumps(data))) errmsg = None if "actionFields" not in data: errmsg = "missing actionFields" else: fields = data["actionFields"] expected_fields = ["account", "card"] for field in expected_fields: if field not in fields: errmsg = "missing field: " + field if errmsg: print("[change_card_account] 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("Card"): if acc["iban"] == fields["account"]: accountid = acc["id"] if accountid is None: errmsg = "unknown account: " + fields["account"] print("[change_card_account] ERROR: " + errmsg) return json.dumps({"errors": [{"status": "SKIP", "message": errmsg}]})\ , 400 if "pin_ordinal" in fields: pinord = fields["pin_ordinal"] else: pinord = "PRIMARY" msg = { "pin_code_assignment": [{ "type": pinord, "monetary_account_id": int(accountid), }] } config = bunq.retrieve_config() data = bunq.get("v1/user/{}/card".format(config["user_id"]), config) for item in data["Response"]: for typ in item: card = item[typ] if str(card["id"]) == str(fields["card"]): for pca in card["pin_code_assignment"]: if pca["type"] != pinord: msg["pin_code_assignment"].append({ "type": pca["type"], "monetary_account_id": pca["monetary_account_id"] }) res = bunq.session_request_encrypted( "PUT", "v1/user/{}/card/{}".format(config["user_id"], fields["card"]), msg, config) if "Error" in res: print(json.dumps(res)) errmsg = "Bunq API call failed, see the logs!" return json.dumps({"errors": [{"status": "SKIP", "message": errmsg}]})\ , 400 return json.dumps({"data": [{"id": uuid.uuid4().hex}]})
def get_callbacks(user_id, account_id): method = 'v1/user/{0}/monetary-account/{1}'.format(user_id, account_id) result = bunq.get(method) return result[0]["MonetaryAccountJoint"]["notification_filters"]
else: # Preserve any other callback new_notifications.append(nf) if not removed_notification: print("Adding callback...") new_notifications.append({ "category": args.toggle_category, "notification_delivery_method": "URL", "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 = "v1/user/{}/monetary-account-bank/{}".format( bunq_user_id, bunq_account_id) result = bunq.get(method) old_nfs = result[0]["MonetaryAccountBank"]["notification_filters"] new_nfs = update_notifications(old_nfs) bunq.put(method, {"notification_filters": new_nfs}) else: method = "v1/user-person/{}".format(bunq_user_id) result = bunq.get(method) old_nfs = result[0]["UserPerson"]["notification_filters"] new_nfs = update_notifications(old_nfs) bunq.put(method, {"notification_filters": new_nfs})
def print_notification_filter(nfs): if not nfs: print(" No callbacks") return for nfi in nfs: nf = nfi["NotificationFilterUrl"] print(' {} -> {}'.format( nf["category"], nf.get("notification_target", "-"))) bunq_user_id = bunq_api.get_user_id(args.bunq_user_name) method = f"v1/user/{bunq_user_id}/notification-filter-url" nfs = bunq.get(method) print("Callbacks for user:"******"v1/user/{bunq_user_id}/monetary-account" for acs in bunq.get(method): for ac in acs.values(): account_id = ac["id"] print(f'Callbacks for account {account_id} "{ac["description"]}":') method = (f"v1/user/{bunq_user_id}/monetary-account/{account_id}/" + "notification-filter-url") nfs = bunq.get(method) print_notification_filter(nfs)
import bunq parser = argparse.ArgumentParser() parser.add_argument("-v", help="Show content of JSON messages", action="store_true") parser.add_argument("-vv", help="Show JSON messages and HTTP headers", action="store_true") args = parser.parse_args() log_level = 2 if args.vv else 1 if args.v else 0 bunq.set_log_level(log_level) def print_accounts(userid): method = 'v1/user/{0}/monetary-account'.format(userid) for a in bunq.get(method): for k, v in a.items(): print(" {}".format(k)) print(" {0:28} {1:10,} {2:3} ({3})".format( v["description"], Decimal(v["balance"]["value"]), v["balance"]["currency"], v["id"])) users = bunq.get('v1/user') for u in users: for k, v in u.items(): print('{0} "{1}" ({2})'.format(k, v["display_name"], v["id"])) print_accounts(v["id"])