示例#1
0
    def setUp(self):
        self.app = create_app('TEST')
        self.app.testing = True
        self.test_client = self.app.test_client()

        self.parameters = {
            'reference_number': 'braintree_reference_number',
            'user_exists_id': 5,
            'user_new_id': '67',
            'customer_id': 'customer_id',
            'method_used': 'Web Form Credit Card',
            'given_to': 'NERF',
            'transaction_status': 'Completed',
            'transaction_type': 'Gift',
            'gross_gift_amount': Decimal(25.00),
            'fee': Decimal(0.00)
        }

        init_braintree_credentials(self.app)

        with self.app.app_context():
            database.reflect()
            database.drop_all()
            database.create_all()

            # Create some ultsys user data for the Ultsys endpoints wrapped in functions for mocking.
            create_ultsys_users()

            database.session.add_all(create_method_used())
            database.session.commit()
            self.method_used_id = MethodUsedModel.get_method_used(
                'name', self.parameters['method_used']).id
示例#2
0
def get_braintree_token():
    """Handle token generation for Braintree API.

    The front-end uses hosted fields and requires a Braintree token to make a submission for a sale. On submission
    a payment nonce will be returned to the back-end to create the Transaction.sale( {} ). The initial token is
    created with a call to this endpoint.

    :return: Braintree token.
    """

    init_braintree_credentials(current_app)

    return generate_braintree_token()
    def setUp(self):
        self.app = create_app('TEST')
        self.app.testing = True
        self.test_client = self.app.test_client()

        self.parameters = {}

        init_braintree_credentials(self.app)

        with self.app.app_context():
            database.reflect()
            database.drop_all()
            database.create_all()

            database.session.add_all(create_method_used())
            database.session.commit()
示例#4
0
def admin_get_braintree_sale_status(transaction_id):
    """A function for getting the Braintree status of a sale.

    :param dict transaction_id: The gift searchable ID associated with the Braintree sale.
    :return: Braintree status.
    """

    init_braintree_credentials(current_app)

    try:
        transaction = TransactionModel.query.filter_by(id=transaction_id).one()
        braintree_id = transaction.reference_number
        braintree_transaction = get_braintree_transaction(braintree_id)
    except AdminTransactionModelPathError as error:
        logging.exception(error.message)
        return False

    return braintree_transaction.status
示例#5
0
def reallocate_subscription( recurring_subscription_id, reallocate_to ):
    """A function for reallocating a Braintree gift subscription.

    :param recurring_subscription_id: The Braintree subscription ID from the gift.
    :param reallocate_to: The account to reallocate the donation to.
    :return: Braintree subscription
    """

    # Configure Braintree.
    init_braintree_credentials( current_app )

    merchant_account_id = {
        'NERF': current_app.config[ 'NUMBERSUSA' ],
        'ACTION': current_app.config[ 'NUMBERSUSA_ACTION' ]
    }

    # This is an administrative function and we allow them to grab a default payment method.
    subscription = braintree.Subscription.find( recurring_subscription_id )

    # Getting this far, we can now update the subscription to the new plan and merchant account ID as required.
    # The original Braintree transaction maintains the same merchant account ID for historical significance.
    # The original Braintree transaction that is reallocated will have the new subscription plan ID.
    # New Braintree transactions from the subscription will have new merchant account ID/subscription plan ID.
    braintree_subscription = braintree.Subscription.update(
        recurring_subscription_id,
        {
            'id': recurring_subscription_id,
            'payment_method_token': subscription.payment_method_token,
            'plan_id': merchant_account_id[ reallocate_to ],
            'merchant_account_id': merchant_account_id[ reallocate_to ]
        }
    )
    if not braintree_subscription.is_success:
        errors = handle_braintree_errors( braintree_subscription )
        logging.exception( AdminUpdateSubscriptionPathError( errors=errors ).message )

    return braintree_subscription
示例#6
0
from time import sleep

import braintree

from application.app import create_app
from application.flask_essentials import database
from application.helpers.braintree_api import init_braintree_credentials
from application.helpers.model_serialization import from_json
from application.models.agent import AgentModel
from application.models.transaction import TransactionModel
from application.schemas.gift import GiftSchema
from application.schemas.transaction import TransactionSchema

app = create_app('DEV')  # pylint: disable=C0103

init_braintree_credentials(app)

MODEL_DATE_STRING_FORMAT = '%Y-%m-%d %H:%M:%S'
BRAINTREE_DATE_STRING_FORMAT = '%Y-%m-%dT%H:%M:%SZ'

DEFAULT_DATE_IN_UTC = datetime.fromtimestamp(0).strftime(
    MODEL_DATE_STRING_FORMAT)

VALID_CARD_NUMBER = '4111111111111111'
DISPUTE_CARD_NUMBER = '4023898493988028'
AMOUNT_VALID = '20.00'
AMOUNT_GATEWAY_REJECTED = '5001.00'
AMOUNT_PROCESSOR_DECLINED = '2000.00'

MERCHANT_ACCOUNT_ID = {'NERF': 'numbersusa', 'ACTION': 'numbersusa_action'}
示例#7
0
def void_transaction(payload):
    """A function for voiding a Braintree transaction on a gift.

    Find the transaction in the database and get the Braintree transaction number. Configure the Braintree API and
    void the transaction through Braintree.

    payload = {
        "transaction_id": 1,
        "user_id": "1234",
        "transaction_notes": "Some transaction notes."
    }

    :param dict payload: A dictionary that provides information to void the transaction.
    :return:
    """

    # Retrieve the transaction that is to be voided.
    try:
        transaction_model = TransactionModel.query.filter_by(
            id=payload['transaction_id']).one()
    except:
        raise AdminTransactionModelPathError('voided')

    try:
        braintree_id = transaction_model.reference_number
        # Need dictionary for schema, and schema.load will not include the gift_id by design.
        transaction_data = to_json(TransactionSchema(), transaction_model)
        transaction_json = transaction_data[0]
        transaction_json['gift_id'] = transaction_model.gift_id
        transaction_json['notes'] = payload['transaction_notes']
    except:
        raise AdminBuildModelsPathError()

    # Generate Braintree void, where make_braintree_void() returns a Braintree voided transaction.
    init_braintree_credentials(current_app)

    transaction_void = make_braintree_void(braintree_id)

    # Need to attach the user who is doing the void.
    enacted_by_agent = AgentModel.get_agent('Staff Member', 'user_id',
                                            payload['user_id'])
    transaction_json['enacted_by_agent_id'] = enacted_by_agent.id

    try:
        # Use BraintreeSaleSchema to populate gift and transaction dictionaries.
        braintree_schema = BraintreeSaleSchema()
        braintree_schema.context = {
            'gift': {},
            'transaction': transaction_json
        }
        braintree_sale = braintree_schema.dump(transaction_void.transaction)
        transaction_json = braintree_sale.data['transaction']

        gift_model = GiftModel.query.get(transaction_json['gift_id'])
        gross_amount = gift_model.transactions[0].gross_gift_amount
        transaction_json['gross_gift_amount'] += gross_amount

        transaction_void_model = from_json(TransactionSchema(),
                                           transaction_json)
        database.session.add(transaction_void_model.data)
        database.session.commit()
        database.session.flush()
        transaction_json['id'] = transaction_void_model.data.id
    except:
        raise AdminBuildModelsPathError()

    return transaction_json
示例#8
0
def post_donation(payload):
    """ Handle individual donation by calling Braintree API if 'Online', else build the transaction here.

    Calls the function make_braintree_sale( payload ) or make_admin_sale( payload ), both located in helpers, to
    handle the donation. The first incorporates the Braintree API to make the sale, the second does not.

    The Braintree API will create a customer in the vault if needed, register a subscription, and make the sale.
    It will return errors if any occur.

    Both sales call a caging function to categorize the donor. Once the sale is made gift, transaction, and user
    dictionaries are returned and the model updates managed in the present function.

    payload = {
        "gift": {
            "method_used": "Web Form Credit Card",
            "date_of_method_used": datetime,
            "given_to": "NERF"
        },
        "transaction": {
            "gross_gift_amount": "10.00",
            "notes": "Some notes for the transaction."
        },
        "user": {
            "user_id": null,
            "user_address": {
              "user_first_name": "Ralph",
              "user_last_name": "Kramden",
              "user_zipcode": "11214",
              "user_address": "328 Chauncey St",
              "user_city": "Bensonhurst",
              "user_state": "NY",
              "user_email_address": "*****@*****.**",
              "user_phone_number": "9172307441"
            },
            "billing_address": {
              "billing_first_name": "Ralph",
              "billing_last_name": "Kramden",
              "billing_zipcode": "11214",
              "billing_address": "7001 18th Ave",
              "billing_city": "Bensonhurst",
              "billing_state": "NY",
              "billing_email_address": "*****@*****.**",
              "billing_phone_number": "9172307441"
            }
        },
        "payment_method_nonce": "fake-valid-visa-nonce",
        "recurring_subscription": false
    }

    If this is an administrative donation then there needs to be 3 additional fields on the payload:

        payload[ 'transaction' ][ 'date_of_method_used' ] = 2018-10-04 00:00:00, e.g. the date on the check.
        payload[ "transaction" ][ "reference_number" ] = "101"
        payload[ "transaction" ][ "bank_deposit_number" ] = "<bank_deposit_number>"

    The agent ID is extracted from the JWT token and placed on the payload if applicable.


    :param dict payload: Dictionary of needed information to make a donation.
    :return: Boolean for success or failure.
    """

    init_braintree_credentials(current_app)

    # This is a fix to a mismatch between what the back-end expects and what the front-end is passing.
    user = payload.pop('user')
    user = validate_user_payload(user)
    payload['user'] = user

    # If method used is Web Form Credit Card or Admin-Entered Credit Card then make a Braintree sale, otherwise is one
    # of the other administrative requests, e.g. Check, Money Order, Stock, Cash, Other.
    braintree_sale = payload[ 'gift' ][ 'method_used' ].lower() == 'web form credit card' or \
        payload[ 'gift' ][ 'method_used' ].lower() == 'web form paypal' or \
        payload[ 'gift' ][ 'method_used' ].lower() == 'admin-entered credit card'

    # See if we need to make a Braintree sale, that is Online or Credit Card call Braintree API.
    # Since merchant account ID is not set for Support a donation to that cannot be made ( raises an error ).
    # Otherwise make the administrative sale, e.g. check, stock, wire transfer, etc.
    # If the code fails in the conditional the database is not modified and the exception is handled by
    # app.errorhandler().
    if braintree_sale and payload['gift']['given_to'].lower() != 'support':
        donation = make_braintree_sale(payload, current_app)
    elif braintree_sale and payload['gift']['given_to'].lower() == 'support':
        raise ModelGiftImproperFieldError
    else:
        donation = make_admin_sale(payload)

    # Getting ready to save to the database, and want to prevent orphaned gifts/transactions.
    # The response sent back will have redis job ID, status, and the gift searchable ID.
    response = {}
    try:
        # Call build_models_sale() and if an exception occurs while building gift/transaction roll back and quit.
        # If the gift/transaction is made then see about the Thank You letter and the email. If either fails
        # log and move on.
        build_models_sale(donation['user'], donation['gift'],
                          donation['transactions'])

        # If the gift amount >= $100 ( current threshold ), add to gift_thank_you_letter table
        if Decimal( donation[ 'transactions' ][ 0 ][ 'gross_gift_amount' ] ) \
                >= Decimal( current_app.config[ 'THANK_YOU_LETTER_THRESHOLD' ] ):
            try:
                database.session.add(
                    GiftThankYouLetterModel(
                        gift_id=donation['transactions'][0]['gift_id']))
                database.session.commit()
            except:
                database.session.rollback()
                logging.exception(DonateBuildModelPathError().message)

        try:
            recurring = False
            if 'recurring_subscription_id' in donation['gift'] and donation[
                    'gift']['recurring_subscription_id']:
                recurring = True
            send_admin_email(donation['transactions'][0], donation['user'],
                             recurring)
        except (SendAdminEmailModelError, EmailSendPathError,
                BuildEmailPayloadPathError, EmailHTTPStatusError) as error:
            logging.exception(error.message)

        response['gift_searchable_id'] = str(
            donation['user']['gift_searchable_id'])
    except BuildModelsGiftTransactionsPathError as error:
        logging.exception(error.message)

    # Build the queued donor model with whatever information we have from above.
    # It can still help construct what happened if there is an error higher up.
    try:
        build_model_queued_donor(donation['user'])
    except BuildModelsQueuedDonorPathError as error:
        logging.exception(error.message)

    # Once on the queue it is out of our hands, but may fail on arguments to queue().
    job = None
    try:
        job = redis_queue_caging.queue(donation['user'],
                                       donation['transactions'],
                                       current_app.config['ENV'])
    except:
        pass

    # If the redis job fails quickly, e.g. the ENV is incorrect, the status will be 'failed'.
    # Otherwise, if it takes more time to fail, the status will be 'queued', 'started', etc.
    # We get what status we can and pass it back.
    response['job_id'] = None
    response['job_status'] = None
    if job:
        response['job_id'] = job.get_id()
        response['job_status'] = job.get_status()

        logging.debug('REDIS CAGING: %s', response)

    return response
def refund_transaction(payload):
    """A function for refunding a Braintree transaction on a gift.

    Find the transaction in the database and get the Braintree transaction number. Configure the Braintree API and
    make the refund through Braintree.

    payload = {
        "transaction_id": 1,
        "amount": "0.01",
        "transaction_notes": "Some transaction notes."
    }

    :param dict payload: A dictionary that provides information to make the refund.
    :return:
    :raises MarshmallowValidationError: If Marshmallow throws a validation error.
    :raises SQLAlchemyError: General SQLAlchemy error.
    :raises SQLAlchemyORMNoResultFoundError: The ORM didn't find the table row.
    """

    # Retrieve the transaction that is to be refunded and raise an exception if not found.
    # Don't try to continue to Braintree without a valid reference number.
    try:
        transaction_model = TransactionModel.query.filter_by(
            id=payload['transaction_id']).one()
    except:
        raise AdminTransactionModelPathError(where='parent')

    transactions = TransactionModel.query.filter_by(
        gift_id=transaction_model.gift_id).all()
    current_balance = transactions[-1].gross_gift_amount

    # If the model cannot be built do not continue to Braintree.
    # Raise an exception.
    try:
        # Build transaction dictionary for schema.
        braintree_id = transaction_model.reference_number
        transaction_data = to_json(TransactionSchema(), transaction_model)
        transaction_json = transaction_data.data
        transaction_json['gift_id'] = transaction_model.gift_id
        transaction_json['notes'] = payload['transaction_notes']
    except:
        raise AdminBuildModelsPathError()

    # Configure Braintree so we can generate a refund.
    init_braintree_credentials(current_app)

    # Function make_braintree_refund() returns: a Braintree refund transaction.
    transaction_refund = make_braintree_refund(braintree_id, payload['amount'],
                                               current_balance)

    # Need to attach the user who is doing the reallocation.
    enacted_by_agent = AgentModel.get_agent('Staff Member', 'user_id',
                                            payload['user_id'])
    transaction_json['enacted_by_agent_id'] = enacted_by_agent.id

    try:
        # Use BraintreeSaleSchema to populate gift and transaction dictionaries.
        braintree_schema = BraintreeSaleSchema()
        braintree_schema.context = {
            'gift': {},
            'transaction': transaction_json
        }
        braintree_sale = braintree_schema.dump(transaction_refund.transaction)
        transaction_json = braintree_sale.data['transaction']
        transaction_json.pop('id')

        gift_model = GiftModel.query.get(transaction_json['gift_id'])
        gross_amount = gift_model.transactions[0].gross_gift_amount
        transaction_json['gross_gift_amount'] += gross_amount

        transaction_refund_model = from_json(TransactionSchema(),
                                             transaction_json)
        database.session.add(transaction_refund_model.data)
        database.session.commit()
        database.session.flush()
        transaction_json['id'] = transaction_refund_model.data.id
    except:
        raise AdminBuildModelsPathError()

    return transaction_json