예제 #1
0
파일: main.py 프로젝트: TLaMars/bunq2ifttt
def home_get():
    """ Endpoint for the homepage """
    cookie = request.cookies.get('session')
    if cookie is None or cookie != util.get_session_cookie():
        return render_template("start.html")

    config = bunq.retrieve_config()
    bunqkeymode = config.get("mode")

    iftttkeyset = (util.get_ifttt_service_key("") is not None)
    accounts = util.get_bunq_accounts_with_permissions(config)
    enableexternal = util.get_external_payment_enabled()
    bunq_oauth = storage.get_value("bunq2IFTTT", "bunq_oauth")
    if bunq_oauth is not None and bunqkeymode != "APIkey":
        expire = arrow.get(bunq_oauth["timestamp"] + 90 * 24 * 3600)
        oauth_expiry = "{} ({})".format(expire.humanize(), expire.isoformat())
    else:
        oauth_expiry = None
    # Google AppEngine does not provide fixed ip addresses
    defaultallips = (os.getenv("GAE_INSTANCE") is not None)

    return render_template("main.html",\
        iftttkeyset=iftttkeyset, bunqkeymode=bunqkeymode, accounts=accounts,\
        enableexternal=enableexternal, defaultallips=defaultallips,\
        oauth_expiry=oauth_expiry)
예제 #2
0
파일: main.py 프로젝트: TLaMars/bunq2ifttt
def ifttt_account_options(include_any, enable_key):
    """ Option values for account selection """
    errmsg = check_ifttt_service_key()
    if errmsg:
        return errmsg, 401

    config = bunq.retrieve_config()
    accounts = util.get_bunq_accounts_with_permissions(config)

    if include_any:
        data = {"data": [{"label": "ANY", "value": "ANY"}]}
    else:
        data = {"data": []}

    for acc in accounts:
        if enable_key is None or (enable_key in acc["perms"]
                                  and acc["perms"][enable_key]):
            ibanstr = acc["iban"]
            iban_formatted = ""
            while len(ibanstr) > 4:
                iban_formatted += ibanstr[:4] + " "
                ibanstr = ibanstr[4:]
            iban_formatted += ibanstr
            data["data"].append({
                "label":
                "{} ({})".format(acc["description"], iban_formatted),
                "value":
                acc["iban"]
            })
    return json.dumps(data)
예제 #3
0
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"])
        }]})
예제 #4
0
파일: util.py 프로젝트: mvdwerve/bunq2ifttt
def get_bunq_accounts(permission=None, config=None):
    """ Return the list of accounts for the given permission """
    if config is None:
        config = bunq.retrieve_config()
    result = []
    for acc in config["accounts"]:
        if permission is None or (acc["iban"] in config["permissions"] and \
                           permission in config["permissions"][acc["iban"]] \
                           and config["permissions"][acc["iban"]][permission]):
            result.append(acc)
    return result
예제 #5
0
파일: card.py 프로젝트: mvdwerve/bunq2ifttt
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"])
예제 #6
0
파일: util.py 프로젝트: mvdwerve/bunq2ifttt
def account_change_permission(iban, permission, value):
    """ Change a permission on an account """
    if permission not in ["Internal", "Draft", "Mutation", "Request", "Card"] \
    and not (permission == "External" and get_external_payment_enabled()):
        print("Invalid permission: " + permission)
        return False

    if value not in ["true", "false"]:
        print("Invalid value: " + value)
        return False
    value = (value == "true")

    config = bunq.retrieve_config()
    if "permissions" not in config:
        config["permissions"] = {}

    if iban not in config["permissions"]:
        config["permissions"][iban] = {}

    config["permissions"][iban][permission] = value
    bunq.save_config(config)
    return True
예제 #7
0
파일: util.py 프로젝트: mvdwerve/bunq2ifttt
def update_bunq_accounts():
    """ Update the list of bunq accounts """
    config = bunq.retrieve_config()
    bunq.retrieve_accounts(config)
    sync_permissions(config)
    bunq.save_config(config)
예제 #8
0
파일: card.py 프로젝트: mvdwerve/bunq2ifttt
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}]})
예제 #9
0
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}]})
예제 #10
0
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"])
        }]})
예제 #11
0
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"])
        }]})