Exemple #1
0
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
Exemple #2
0
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))
Exemple #3
0
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))
Exemple #4
0
 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"])
Exemple #5
0
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"]]
Exemple #6
0
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
Exemple #7
0
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
Exemple #8
0
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)
Exemple #9
0
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})
Exemple #10
0
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"]))
Exemple #11
0
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
Exemple #12
0
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))
Exemple #13
0
 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)
Exemple #14
0
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)
Exemple #15
0
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)
Exemple #16
0
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
Exemple #17
0
 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'])
Exemple #18
0
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"])
Exemple #19
0
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
Exemple #20
0
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
Exemple #21
0
def get_callbacks(user_id, account_id):
    method = (f"v1/user/{user_id}/monetary-account/{account_id}/" +
              "notification-filter-url")
    return bunq.get(method)
Exemple #22
0
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]))
Exemple #23
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})
Exemple #24
0
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
Exemple #25
0
    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)
Exemple #26
0
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}]})
Exemple #27
0
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})
Exemple #29
0

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)
Exemple #30
0
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"])