Пример #1
0
def handler(post, path, remoteip, referer, querystring):
    """ Handles the various service method types.
    post:        The GET/POST parameters
    path:        The current system path/code.PATH
    remoteip:    The IP of the caller
    referer:     The referer HTTP header
    querystring: The complete querystring
    return value is a tuple containing MIME type, max-age, content
    """
    # Get service parameters
    account = post["account"]
    username = post["username"]
    password = post["password"]
    method = post["method"]
    animalid = post.integer("animalid")
    formid = post.integer("formid")
    mediaid = post.integer("mediaid")
    seq = post.integer("seq")
    title = post["title"]
    strip_personal = post.integer("sensitive") == 0

    # If this method is in the cache protected list, only use
    # whitelisted parameters for the key to prevent callers
    # cache-busting by adding junk parameters
    cache_key = querystring.replace(" ", "")
    if method in CACHE_PROTECT_METHODS:
        cache_key = safe_cache_key(method, cache_key)

    # Do we have a cached response for these parameters?
    cached_response = get_cached_response(cache_key, account)
    if cached_response is not None:
        asm3.al.debug(
            "cache hit: %s (%d bytes)" % (cache_key, len(cached_response[3])),
            "service.handler", account)
        return cached_response

    # Are we dealing with multiple databases, but no account was specified?
    if account == "" and MULTIPLE_DATABASES:
        return ("text/plain", 0, 0, "ERROR: No database/alias specified")

    # Is flood protection activated for this method?
    if method in FLOOD_PROTECT_METHODS:
        flood_protect(method, remoteip)

    dbo = asm3.db.get_database(account)

    if dbo.database in asm3.db.ERROR_VALUES:
        asm3.al.error(
            "auth failed - invalid smaccount %s from %s (%s)" %
            (account, remoteip, dbo.database), "service.handler", dbo)
        return ("text/plain", 0, 0,
                "ERROR: Invalid database (%s)" % dbo.database)

    # If the database has disabled the service API, stop now
    if not asm3.configuration.service_enabled(dbo):
        asm3.al.error("Service API is disabled (%s)" % method,
                      "service.handler", dbo)
        return ("text/plain", 0, 0, "ERROR: Service API is disabled")

    # Do any database updates need doing in this db?
    dbo.installpath = path
    if asm3.dbupdate.check_for_updates(dbo):
        asm3.dbupdate.perform_updates(dbo)

    # Does the method require us to authenticate? If so, do it.
    user = None
    securitymap = ""
    if method in AUTH_METHODS:
        # If the database has authenticated service methods disabled, stop now
        if not asm3.configuration.service_auth_enabled(dbo):
            asm3.al.error(
                "Service API for auth methods is disabled (%s)" % method,
                "service.handler", dbo)
            return ("text/plain", 0, 0,
                    "ERROR: Service API for authenticated methods is disabled")
        user = asm3.users.authenticate(dbo, username, password)
        if user is None:
            asm3.al.error(
                "auth failed - %s/%s is not a valid username/password from %s"
                % (username, password, remoteip), "service.handler", dbo)
            return ("text/plain", 0, 0, "ERROR: Invalid username and password")
        securitymap = asm3.users.get_security_map(dbo, user["USERNAME"])

    # Get the preferred locale and timezone for the site
    l = asm3.configuration.locale(dbo)
    dbo.locale = l
    dbo.timezone = asm3.configuration.timezone(dbo)
    dbo.timezone_dst = asm3.configuration.timezone_dst(dbo)
    asm3.al.info("call @%s --> %s [%s]" % (username, method, querystring),
                 "service.handler", dbo)

    if method == "animal_image":
        hotlink_protect("animal_image", referer)
        if asm3.utils.cint(animalid) == 0:
            asm3.al.error(
                "animal_image failed, %s is not an animalid" % str(animalid),
                "service.handler", dbo)
            return ("text/plain", 0, 0, "ERROR: Invalid animalid")
        else:
            dummy, data = asm3.media.get_image_file_data(
                dbo, "animal", asm3.utils.cint(animalid), seq)
            if data == "NOPIC":
                dummy, data = asm3.media.get_image_file_data(dbo, "nopic", 0)
            return set_cached_response(cache_key, account, "image/jpeg", 86400,
                                       3600, data)

    elif method == "animal_thumbnail":
        if asm3.utils.cint(animalid) == 0:
            asm3.al.error(
                "animal_thumbnail failed, %s is not an animalid" %
                str(animalid), "service.handler", dbo)
            return ("text/plain", 0, 0, "ERROR: Invalid animalid")
        else:
            dummy, data = asm3.media.get_image_file_data(
                dbo, "animalthumb", asm3.utils.cint(animalid), seq)
            if data == "NOPIC":
                dummy, data = asm3.media.get_image_file_data(dbo, "nopic", 0)
            return set_cached_response(cache_key, account, "image/jpeg", 86400,
                                       3600, data)

    elif method == "animal_view":
        if asm3.utils.cint(animalid) == 0:
            asm3.al.error(
                "animal_view failed, %s is not an animalid" % str(animalid),
                "service.handler", dbo)
            return ("text/plain", 0, 0, "ERROR: Invalid animalid")
        else:
            return set_cached_response(
                cache_key, account, "text/html", 86400, 120,
                asm3.publishers.html.get_animal_view(
                    dbo, asm3.utils.cint(animalid)))

    elif method == "animal_view_adoptable_js":
        return set_cached_response(
            cache_key, account, "application/javascript", 10800, 600,
            asm3.publishers.html.get_animal_view_adoptable_js(dbo))

    elif method == "animal_view_adoptable_html":
        return set_cached_response(
            cache_key, account, "text/html", 86400, 120,
            asm3.publishers.html.get_animal_view_adoptable_html(dbo))

    elif method == "checkout":
        processor = asm3.financial.get_payment_processor(
            dbo, post["processor"])
        if not processor.validatePaymentReference(post["payref"]):
            return ("text/plain", 0, 0, "ERROR: Invalid payref")
        if processor.isPaymentReceived(post["payref"]):
            return ("text/plain", 0, 0, "ERROR: Expired payref")
        return_url = post["return"] or asm3.configuration.payment_return_url(
            dbo)
        return set_cached_response(
            cache_key, account, "text/html", 15, 15,
            processor.checkoutPage(post["payref"], return_url, title))

    elif method == "checkout_adoption":
        if post["token"] == "":
            raise asm3.utils.ASMError(
                "method checkout_adoption requires a valid token")
        elif post["sig"] == "":
            return set_cached_response(
                cache_key, account, "text/html", 120, 120,
                checkout_adoption_page(dbo, post["token"]))
        else:
            return ("text/plain", 0, 0, checkout_adoption_post(dbo, post))

    elif method == "dbfs_image":
        hotlink_protect("dbfs_image", referer)
        return set_cached_response(
            cache_key, account, "image/jpeg", 86400, 86400,
            asm3.utils.iif(title.startswith("/"),
                           asm3.dbfs.get_string_filepath(dbo, title),
                           asm3.dbfs.get_string(dbo, title)))

    elif method == "document_repository":
        return set_cached_response(
            cache_key, account,
            asm3.media.mime_type(asm3.dbfs.get_name_for_id(dbo, mediaid)),
            86400, 86400, asm3.dbfs.get_string_id(dbo, mediaid))

    elif method == "extra_image":
        hotlink_protect("extra_image", referer)
        return set_cached_response(
            cache_key, account, "image/jpeg", 86400, 86400,
            asm3.dbfs.get_string(dbo, title, "/reports"))

    elif method == "media_image":
        hotlink_protect("media_image", referer)
        return set_cached_response(
            cache_key, account, "image/jpeg", 86400, 86400,
            asm3.dbfs.get_string_id(
                dbo,
                dbo.query_int("select dbfsid from media where id = ?",
                              [mediaid])))

    elif method == "json_adoptable_animal":
        if asm3.utils.cint(animalid) == 0:
            asm3.al.error(
                "json_adoptable_animal failed, %s is not an animalid" %
                str(animalid), "service.handler", dbo)
            return ("text/plain", 0, 0, "ERROR: Invalid animalid")
        else:
            asm3.users.check_permission_map(l, user["SUPERUSER"], securitymap,
                                            asm3.users.VIEW_ANIMAL)
            rs = asm3.publishers.base.get_animal_data(
                dbo,
                None,
                asm3.utils.cint(animalid),
                include_additional_fields=True)
            return set_cached_response(cache_key, account, "application/json",
                                       3600, 3600, asm3.utils.json(rs))

    elif method == "html_adoptable_animals":
        return set_cached_response(cache_key, account, "text/html", 600, 600, \
            asm3.publishers.html.get_adoptable_animals(dbo, style=post["template"], \
                speciesid=post.integer("speciesid"), animaltypeid=post.integer("animaltypeid"), \
                locationid=post.integer("locationid"), underweeks=post.integer("underweeks"), \
                overweeks=post.integer("overweeks")))

    elif method == "html_adopted_animals":
        return set_cached_response(cache_key, account, "text/html", 10800, 1800, \
            asm3.publishers.html.get_adopted_animals(dbo, daysadopted=post.integer("days"), style=post["template"], \
                speciesid=post.integer("speciesid"), animaltypeid=post.integer("animaltypeid")))

    elif method == "html_deceased_animals":
        return set_cached_response(cache_key, account, "text/html", 10800, 1800, \
            asm3.publishers.html.get_deceased_animals(dbo, daysdeceased=post.integer("days"), style=post["template"], \
                speciesid=post.integer("speciesid"), animaltypeid=post.integer("animaltypeid")))

    elif method == "html_flagged_animals":
        if post["flag"] == "":
            asm3.al.error("html_flagged_animals requested with no flag.",
                          "service.handler", dbo)
            return ("text/plain", 0, 0, "ERROR: Invalid flag")
        return set_cached_response(cache_key, account, "text/html", 1800, 1800, \
            asm3.publishers.html.get_flagged_animals(dbo, style=post["template"], \
                speciesid=post.integer("speciesid"), animaltypeid=post.integer("animaltypeid"), flag=post["flag"], allanimals=post.integer("all")))

    elif method == "html_held_animals":
        return set_cached_response(cache_key, account, "text/html", 1800, 1800, \
            asm3.publishers.html.get_held_animals(dbo, style=post["template"], \
                speciesid=post.integer("speciesid"), animaltypeid=post.integer("animaltypeid")))

    elif method == "json_adoptable_animals_xp":
        rs = strip_personal_data(
            asm3.publishers.base.get_animal_data(
                dbo, None, include_additional_fields=True))
        return set_cached_response(cache_key, account, "application/json", 600,
                                   600, asm3.utils.json(rs))

    elif method == "json_adoptable_animals":
        asm3.users.check_permission_map(l, user["SUPERUSER"], securitymap,
                                        asm3.users.VIEW_ANIMAL)
        rs = asm3.publishers.base.get_animal_data(
            dbo, None, include_additional_fields=True)
        if strip_personal: rs = strip_personal_data(rs)
        return set_cached_response(cache_key, account, "application/json", 600,
                                   600, asm3.utils.json(rs))

    elif method == "jsonp_adoptable_animals":
        asm3.users.check_permission_map(l, user["SUPERUSER"], securitymap,
                                        asm3.users.VIEW_ANIMAL)
        rs = asm3.publishers.base.get_animal_data(
            dbo, None, include_additional_fields=True)
        if strip_personal: rs = strip_personal_data(rs)
        return ("application/javascript", 0, 0,
                "%s(%s);" % (post["callback"], asm3.utils.json(rs)))

    elif method == "xml_adoptable_animal":
        if asm3.utils.cint(animalid) == 0:
            asm3.al.error(
                "xml_adoptable_animal failed, %s is not an animalid" %
                str(animalid), "service.handler", dbo)
            return ("text/plain", 0, 0, "ERROR: Invalid animalid")
        else:
            asm3.users.check_permission_map(l, user["SUPERUSER"], securitymap,
                                            asm3.users.VIEW_ANIMAL)
            rs = asm3.publishers.base.get_animal_data(
                dbo,
                None,
                asm3.utils.cint(animalid),
                include_additional_fields=True)
            return set_cached_response(cache_key, account, "application/xml",
                                       600, 600, asm3.html.xml(rs))

    elif method == "xml_adoptable_animals":
        asm3.users.check_permission_map(l, user["SUPERUSER"], securitymap,
                                        asm3.users.VIEW_ANIMAL)
        rs = asm3.publishers.base.get_animal_data(
            dbo, None, include_additional_fields=True)
        if strip_personal: rs = strip_personal_data(rs)
        return set_cached_response(cache_key, account, "application/xml", 600,
                                   600, asm3.html.xml(rs))

    elif method == "json_found_animals":
        asm3.users.check_permission_map(l, user["SUPERUSER"], securitymap,
                                        asm3.users.VIEW_FOUND_ANIMAL)
        rs = asm3.lostfound.get_foundanimal_last_days(dbo)
        return set_cached_response(cache_key, account, "application/json",
                                   3600, 3600, asm3.utils.json(rs))

    elif method == "jsonp_found_animals":
        asm3.users.check_permission_map(l, user["SUPERUSER"], securitymap,
                                        asm3.users.VIEW_FOUND_ANIMAL)
        rs = asm3.lostfound.get_foundanimal_last_days(dbo)
        return ("application/javascript", 0, 0,
                "%s(%s);" % (post["callback"], asm3.utils.json(rs)))

    elif method == "xml_found_animals":
        asm3.users.check_permission_map(l, user["SUPERUSER"], securitymap,
                                        asm3.users.VIEW_FOUND_ANIMAL)
        rs = asm3.lostfound.get_foundanimal_last_days(dbo)
        return set_cached_response(cache_key, account, "application/json",
                                   3600, 3600, asm3.html.xml(rs))

    elif method == "json_held_animals":
        asm3.users.check_permission_map(l, user["SUPERUSER"], securitymap,
                                        asm3.users.VIEW_ANIMAL)
        rs = asm3.animal.get_animals_hold(dbo)
        return set_cached_response(cache_key, account, "application/json",
                                   3600, 3600, asm3.utils.json(rs))

    elif method == "xml_held_animals":
        asm3.users.check_permission_map(l, user["SUPERUSER"], securitymap,
                                        asm3.users.VIEW_ANIMAL)
        rs = asm3.animal.get_animals_hold(dbo)
        return set_cached_response(cache_key, account, "application/json",
                                   3600, 3600, asm3.html.xml(rs))

    elif method == "jsonp_held_animals":
        asm3.users.check_permission_map(l, user["SUPERUSER"], securitymap,
                                        asm3.users.VIEW_ANIMAL)
        rs = asm3.animal.get_animals_hold(dbo)
        return ("application/javascript", 0, 0,
                "%s(%s);" % (post["callback"], asm3.utils.json(rs)))

    elif method == "json_lost_animals":
        asm3.users.check_permission_map(l, user["SUPERUSER"], securitymap,
                                        asm3.users.VIEW_LOST_ANIMAL)
        rs = asm3.lostfound.get_lostanimal_last_days(dbo)
        return set_cached_response(cache_key, account, "application/json",
                                   3600, 3600, asm3.utils.json(rs))

    elif method == "jsonp_lost_animals":
        asm3.users.check_permission_map(l, user["SUPERUSER"], securitymap,
                                        asm3.users.VIEW_LOST_ANIMAL)
        rs = asm3.lostfound.get_lostanimal_last_days(dbo)
        return ("application/javascript", 0, 0,
                "%s(%s);" % (post["callback"], asm3.utils.json(rs)))

    elif method == "xml_lost_animals":
        asm3.users.check_permission_map(l, user["SUPERUSER"], securitymap,
                                        asm3.users.VIEW_LOST_ANIMAL)
        rs = asm3.lostfound.get_lostanimal_last_days(dbo)
        return set_cached_response(cache_key, account, "application/json",
                                   3600, 3600, asm3.html.xml(rs))

    elif method == "json_recent_adoptions":
        asm3.users.check_permission_map(l, user["SUPERUSER"], securitymap,
                                        asm3.users.VIEW_ANIMAL)
        rs = asm3.movement.get_recent_adoptions(dbo)
        return set_cached_response(cache_key, account, "application/json",
                                   3600, 3600, asm3.utils.json(rs))

    elif method == "jsonp_recent_adoptions":
        asm3.users.check_permission_map(l, user["SUPERUSER"], securitymap,
                                        asm3.users.VIEW_ANIMAL)
        rs = asm3.movement.get_recent_adoptions(dbo)
        return ("application/javascript", 0, 0,
                "%s(%s);" % (post["callback"], asm3.utils.json(rs)))

    elif method == "xml_recent_adoptions":
        asm3.users.check_permission_map(l, user["SUPERUSER"], securitymap,
                                        asm3.users.VIEW_ANIMAL)
        rs = asm3.movement.get_recent_adoptions(dbo)
        return set_cached_response(cache_key, account, "application/xml", 3600,
                                   3600, asm3.html.xml(rs))

    elif method == "html_report":
        asm3.users.check_permission_map(l, user["SUPERUSER"], securitymap,
                                        asm3.users.VIEW_REPORT)
        crid = asm3.reports.get_id(dbo, title)
        p = asm3.reports.get_criteria_params(dbo, crid, post)
        rhtml = asm3.reports.execute(dbo, crid, username, p)
        rhtml = asm3.utils.fix_relative_document_uris(dbo, rhtml)
        return set_cached_response(cache_key, account, "text/html", 600, 600,
                                   rhtml)

    elif method == "csv_mail" or method == "csv_report":
        asm3.users.check_permission_map(l, user["SUPERUSER"], securitymap,
                                        asm3.users.VIEW_REPORT)
        crid = asm3.reports.get_id(dbo, title)
        p = asm3.reports.get_criteria_params(dbo, crid, post)
        rows, cols = asm3.reports.execute_query(dbo, crid, username, p)
        mcsv = asm3.utils.csv(l, rows, cols, True)
        return set_cached_response(cache_key, account, "text/csv", 600, 600,
                                   mcsv)

    elif method == "json_report" or method == "json_mail":
        asm3.users.check_permission_map(l, user["SUPERUSER"], securitymap,
                                        asm3.users.VIEW_REPORT)
        crid = asm3.reports.get_id(dbo, title)
        p = asm3.reports.get_criteria_params(dbo, crid, post)
        rows, cols = asm3.reports.execute_query(dbo, crid, username, p)
        return set_cached_response(cache_key, account, "application/json", 600,
                                   600, asm3.utils.json(rows))

    elif method == "jsonp_report" or method == "jsonp_mail":
        asm3.users.check_permission_map(l, user["SUPERUSER"], securitymap,
                                        asm3.users.VIEW_REPORT)
        crid = asm3.reports.get_id(dbo, title)
        p = asm3.reports.get_criteria_params(dbo, crid, post)
        rows, cols = asm3.reports.execute_query(dbo, crid, username, p)
        return ("application/javascript", 0, 0,
                "%s(%s);" % (post["callback"], asm3.utils.json(rows)))

    elif method == "jsonp_recent_changes":
        asm3.users.check_permission_map(l, user["SUPERUSER"], securitymap,
                                        asm3.users.VIEW_ANIMAL)
        sa = asm3.animal.get_recent_changes(dbo)
        return ("application/javascript", 0, 0,
                "%s(%s);" % (post["callback"], asm3.utils.json(sa)))

    elif method == "json_recent_changes":
        asm3.users.check_permission_map(l, user["SUPERUSER"], securitymap,
                                        asm3.users.VIEW_ANIMAL)
        sa = asm3.animal.get_recent_changes(dbo)
        return set_cached_response(cache_key, account, "application/json",
                                   3600, 3600, asm3.utils.json(sa))

    elif method == "xml_recent_changes":
        asm3.users.check_permission_map(l, user["SUPERUSER"], securitymap,
                                        asm3.users.VIEW_ANIMAL)
        sa = asm3.animal.get_recent_changes(dbo)
        return set_cached_response(cache_key, account, "application/xml", 3600,
                                   3600, asm3.html.xml(sa))

    elif method == "jsonp_shelter_animals":
        asm3.users.check_permission_map(l, user["SUPERUSER"], securitymap,
                                        asm3.users.VIEW_ANIMAL)
        sa = asm3.animal.get_shelter_animals(dbo)
        if strip_personal: sa = strip_personal_data(sa)
        return ("application/javascript", 0, 0,
                "%s(%s);" % (post["callback"], asm3.utils.json(sa)))

    elif method == "json_shelter_animals":
        asm3.users.check_permission_map(l, user["SUPERUSER"], securitymap,
                                        asm3.users.VIEW_ANIMAL)
        sa = asm3.animal.get_shelter_animals(dbo)
        if strip_personal: sa = strip_personal_data(sa)
        return set_cached_response(cache_key, account, "application/json",
                                   3600, 3600, asm3.utils.json(sa))

    elif method == "xml_shelter_animals":
        asm3.users.check_permission_map(l, user["SUPERUSER"], securitymap,
                                        asm3.users.VIEW_ANIMAL)
        sa = asm3.animal.get_shelter_animals(dbo)
        if strip_personal: sa = strip_personal_data(sa)
        return set_cached_response(cache_key, account, "application/xml", 3600,
                                   3600, asm3.html.xml(sa))

    elif method == "rss_timeline":
        asm3.users.check_permission_map(l, user["SUPERUSER"], securitymap,
                                        asm3.users.VIEW_ANIMAL)
        return set_cached_response(cache_key, account, "application/rss+xml",
                                   3600, 3600, asm3.html.timeline_rss(dbo))

    elif method == "upload_animal_image":
        asm3.users.check_permission_map(l, user["SUPERUSER"], securitymap,
                                        asm3.users.ADD_MEDIA)
        asm3.media.attach_file_from_form(dbo, username, asm3.media.ANIMAL,
                                         int(animalid), post)
        return ("text/plain", 0, 0, "OK")

    elif method == "online_form_html":
        if formid == 0:
            raise asm3.utils.ASMError(
                "method online_form_html requires a valid formid")
        return set_cached_response(
            cache_key, account, "text/html; charset=utf-8", 120, 120,
            asm3.onlineform.get_onlineform_html(dbo, formid))

    elif method == "online_form_json":
        if formid == 0:
            raise asm3.utils.ASMError(
                "method online_form_json requires a valid formid")
        return set_cached_response(
            cache_key, account, "application/json; charset=utf-8", 30, 30,
            asm3.onlineform.get_onlineform_json(dbo, formid))

    elif method == "online_form_post":
        asm3.onlineform.insert_onlineformincoming_from_form(
            dbo, post, remoteip)
        redirect = post["redirect"]
        if redirect == "":
            redirect = BASE_URL + "/static/pages/form_submitted.html"
        return ("redirect", 0, 0, redirect)

    elif method == "sign_document":
        if formid == 0:
            raise asm3.utils.ASMError(
                "method sign_document requires a valid formid")
        if post["sig"] == "":
            m = asm3.media.get_media_by_id(dbo, formid)
            if m is None: raise asm3.utils.ASMError("invalid link")
            token = asm3.utils.md5_hash_hex("%s%s" % (m.ID, m.LINKID))
            if token != post["token"]:
                raise asm3.utils.ASMError("invalid token")
            return set_cached_response(
                cache_key, account, "text/html", 2, 2,
                sign_document_page(dbo, formid, post["email"]))
        else:
            signdate = "%s %s" % (python2display(
                l, dbo.now()), format_time(dbo.now()))
            asm3.media.sign_document(dbo, "service", formid, post["sig"],
                                     signdate, "signemail")
            asm3.media.create_log(dbo, "service", formid, "ES02",
                                  _("Document signed", l))
            if post.boolean("sendsigned"):
                m = asm3.media.get_media_by_id(dbo, formid)
                if m is None:
                    raise asm3.utils.ASMError("cannot find %s" % formid)
                content = asm3.utils.bytes2str(
                    asm3.dbfs.get_string(dbo, m.MEDIANAME))
                contentpdf = asm3.utils.html_to_pdf(dbo, content)
                attachments = [("%s.pdf" % m.ID, "application/pdf", contentpdf)
                               ]
                fromaddr = asm3.configuration.email(dbo)
                asm3.utils.send_email(dbo, fromaddr, post["email"], "", "",
                                      _("Signed Document", l), m.MEDIANOTES,
                                      "plain", attachments)
            return ("text/plain", 0, 0, "OK")

    else:
        asm3.al.error("invalid method '%s'" % method, "service.handler", dbo)
        raise asm3.utils.ASMError("Invalid method '%s'" % method)
Пример #2
0
def checkout_adoption_post(dbo, post):
    """
    Called by the adoption checkout frontend with the document signature and donation amount.
    Handles the document signing, triggers creation of the payment records, etc.
    Returns the URL needed to redirect to the payment processor to complete payment.
    """
    l = dbo.locale
    co = asm3.cachedisk.get(post["token"], dbo.database)
    if co is None:
        raise asm3.utils.ASMError("invalid token")
    mediaid = co["mediaid"]
    donationamt = post.integer("donationamt") * 100
    # Sign the docs if they haven't been done already
    if not asm3.media.has_signature(dbo, mediaid):
        signdate = "%s %s" % (python2display(
            l, dbo.now()), format_time(dbo.now()))
        asm3.media.sign_document(dbo, "service", mediaid, post["sig"],
                                 signdate, "signemail")
        if post.boolean("sendsigned"):
            m = asm3.media.get_media_by_id(dbo, mediaid)
            if m is None: raise asm3.utils.ASMError("cannot find %s" % mediaid)
            content = asm3.utils.bytes2str(
                asm3.dbfs.get_string(dbo, m.MEDIANAME))
            contentpdf = asm3.utils.html_to_pdf(dbo, content)
            attachments = [("%s.pdf" % m.ID, "application/pdf", contentpdf)]
            asm3.utils.send_email(dbo, asm3.configuration.email(dbo),
                                  co["email"], "", "", _("Signed Document", l),
                                  m.MEDIANOTES, "plain", attachments)
    # Create the due payment records if they haven't been done already, along with a receipt/payref
    if co["paymentfeeid"] == 0:
        co["paymentprocessor"] = asm3.configuration.adoption_checkout_processor(
            dbo)
        co["receiptnumber"] = asm3.financial.get_next_receipt_number(
            dbo)  # Both go on the same receipt
        co["payref"] = "%s-%s" % (co["personcode"], co["receiptnumber"])
        # Adoption Fee
        co["paymentfeeid"] = asm3.financial.insert_donation_from_form(
            dbo, "checkout",
            asm3.utils.PostedData(
                {
                    "person":
                    str(co["personid"]),
                    "animal":
                    str(co["animalid"]),
                    "movement":
                    str(co["movementid"]),
                    "type":
                    asm3.configuration.adoption_checkout_feeid(dbo),
                    "payment":
                    asm3.configuration.adoption_checkout_payment_method(dbo),
                    "amount":
                    co["fee"],
                    "due":
                    python2display(l, dbo.now()),
                    "receiptnumber":
                    co["receiptnumber"],
                    "giftaid":
                    str(co["giftaid"])
                }, l))
        # Donation (not linked to movement on purpose to avoid showing on adoption fee reports)
        if donationamt > 0:
            co["paymentdonid"] = asm3.financial.insert_donation_from_form(
                dbo, "checkout",
                asm3.utils.PostedData(
                    {
                        "person":
                        str(co["personid"]),
                        "animal":
                        str(co["animalid"]),
                        "type":
                        str(
                            asm3.configuration.adoption_checkout_donationid(
                                dbo)),
                        "payment":
                        str(
                            asm3.configuration.
                            adoption_checkout_payment_method(dbo)),
                        "amount":
                        str(donationamt),
                        "due":
                        python2display(l, dbo.now()),
                        "receiptnumber":
                        co["receiptnumber"],
                        "giftaid":
                        str(co["giftaid"])
                    }, l))
        # Update the cache entry
        asm3.cachedisk.put(post["token"], dbo.database, co, 86400 * 2)
    elif co["paymentdonid"] > 0 and donationamt > 0:
        # payments have already been created, must be a user revisiting the checkout.
        # update their donation amount in case they made a different choice this time.
        dbo.update("ownerdonation", co["paymentdonid"],
                   {"Donation": donationamt}, "checkout")
    elif co["paymentdonid"] > 0 and donationamt == 0:
        # The user has changed their voluntary donation amount to 0 - delete it
        dbo.delete("ownerdonation", co["paymentdonid"], "checkout")
    # Construct the payment checkout URL
    params = {
        "account":
        dbo.database,
        "method":
        "checkout",
        "processor":
        co["paymentprocessor"],
        "payref":
        co["payref"],
        "title":
        _("{0}: Adoption fee") +
        asm3.utils.iif(co["paymentdonid"] != "0", _(" + donation"), "")
    }
    url = "%s?%s" % (SERVICE_URL, asm3.utils.urlencode(params))
    return url