Esempio n. 1
0
def create_stripe_charge(transaction, card_source_id) -> stripe.Charge:

    if transaction.status != Transaction.PENDING:
        raise InternalServerError(
            f"unexpected status of transaction",
            log=
            f"transaction {transaction.id} has unexpected status {transaction.status}"
        )

    stripe_amount = convert_to_stripe_amount(transaction.amount)

    try:
        return stripe.Charge.create(
            amount=stripe_amount,
            currency=CURRENCY,
            description=f'charge for transaction id {transaction.id}',
            source=card_source_id,
        )
    except InvalidRequestError as e:
        raise_from_stripe_invalid_request_error(e)

    except CardError as e:
        error = e.json_body.get('error', {})
        raise PaymentFailed(message=error.get("message"),
                            log=f"stripe charge failed: {str(error)}")

    except StripeError as e:
        raise InternalServerError(
            log=f"stripe charge failed (possibly temporarily): {str(e)}")
Esempio n. 2
0
def process_cart(member_id, cart):
    contents = []
    with localcontext() as ctx:
        ctx.clear_flags()
        total_amount = Decimal(0)

        for item in cart:
            try:
                product_id = item['id']
                product = db_session.query(Product).filter(
                    Product.id == product_id,
                    Product.deleted_at.is_(None)).one()
            except NoResultFound:
                raise NotFound(
                    message=f"Could not find product with id {product_id}.")

            if product.price < 0:
                raise InternalServerError(
                    log=f"Product {product_id} has a negative price.")

            count = item['count']

            if count <= 0:
                raise BadRequest(
                    message=f"Bad product count for product {product_id}.",
                    what=NEGATIVE_ITEM_COUNT)

            if count % product.smallest_multiple != 0:
                raise BadRequest(
                    f"Bad count for product {product_id}, must be in multiples "
                    f"of {product.smallest_multiple}, was {count}.",
                    what=INVALID_ITEM_COUNT)

            if product.filter:
                PRODUCT_FILTERS[product.filter](cart_item=item,
                                                member_id=member_id)

            amount = product.price * count
            total_amount += amount

            content = TransactionContent(product_id=product_id,
                                         count=count,
                                         amount=amount)
            contents.append(content)

        if ctx.flags[Rounded]:
            # This can possibly happen with huge values, I suppose they will be caught below anyway but it's good to
            # catch in any case.
            raise InternalServerError(
                log="Rounding error when calculating cart sum.")

    return total_amount, contents
Esempio n. 3
0
def create_action_required_response(transaction, payment_intent):
    """ The payment_intent requires customer action to be confirmed. Create response to client"""

    try:
        db_session.add(
            StripePending(transaction_id=transaction.id,
                          stripe_token=payment_intent.id))

        if payment_intent.next_action.type == PaymentIntentNextActionType.USE_STRIPE_SDK:
            return dict(type=PaymentIntentNextActionType.USE_STRIPE_SDK,
                        client_secret=payment_intent.client_secret)

        elif payment_intent.next_action.type == PaymentIntentNextActionType.REDIRECT_TO_URL:
            raise InternalServerError(
                log=
                f"unexpected next_action type, {payment_intent.next_action.type}"
            )

        else:
            raise PaymentFailed(
                log=
                f"unknown next_action type, {payment_intent.next_action.type}")

    except Exception:
        # Fail transaction on all known and unknown errors to be safe, we won't charge a failed transaction.
        commit_fail_transaction(transaction)
        logger.info(
            f"failing transaction {transaction.id}, due to error when processing 3ds card"
        )
        raise
Esempio n. 4
0
    def create(self, data=None, commit=True):
        if data is None:
            data = request.json or {}

        handle_password(data)

        status, = db_session.execute(
            "SELECT GET_LOCK('member_number', 20)").fetchone()
        if not status:
            raise InternalServerError(
                "Failed to create member, try again later.",
                log="failed to aquire member_number lock")
        try:
            if data.get('member_number') is None:
                sql = "SELECT COALESCE(MAX(member_number), 999) FROM membership_members"
                max_member_number, = db_session.execute(sql).fetchone()
                data['member_number'] = max_member_number + 1
            obj = self.to_obj(self._create_internal(data, commit=commit))
            return obj
        except Exception:
            # Rollback session if anything went wrong or we can't release the lock.
            db_session.rollback()
            raise
        finally:
            db_session.execute("DO RELEASE_LOCK('member_number')")
Esempio n. 5
0
def charge_transaction(transaction, charge):
    if charge.status != ChargeStatus.SUCCEEDED:
        raise InternalServerError(
            log=
            f"unexpected charge status '{charge.status}' for transaction {transaction.id} "
            f"this should be handled")

    payment_success(transaction)
Esempio n. 6
0
def complete_payment_intent_transaction(transaction, payment_intent):
    if payment_intent.status != PaymentIntentStatus.SUCCEEDED:
        raise InternalServerError(
            log=
            f"unexpected payment_intent status '{payment_intent.status}' for transaction {transaction.id} "
            f"this should be handled")

    if transaction.status == Transaction.PENDING:
        payment_success(transaction)
Esempio n. 7
0
def convert_to_stripe_amount(amount: Decimal) -> int:
    """ Convert decimal amount to stripe amount and return it. Fails if amount is not even cents (ören). """
    stripe_amount = amount * STRIPE_CURRENTY_BASE
    if stripe_amount % 1 != 0:
        raise InternalServerError(
            message=
            f"The amount could not be converted to an even number of ören ({amount}).",
            log=
            f"Stripe amount not even number of ören, maybe some product has uneven ören."
        )

    return int(stripe_amount)
Esempio n. 8
0
def get_source_transaction(source_id):
    try:
        return db_session\
            .query(Transaction)\
            .filter(Transaction.stripe_pending.any(StripePending.stripe_token == source_id))\
            .with_for_update()\
            .one()
    except NoResultFound as e:
        return None
    except MultipleResultsFound as e:
        raise InternalServerError(
            log=
            f"stripe token {source_id} has multiple transactions, this is a bug"
        ) from e
Esempio n. 9
0
def create_client_response(transaction, payment_intent):

    if payment_intent.status == PaymentIntentStatus.REQUIRES_ACTION:
        """ Requires further action on client side. """
        if not payment_intent.next_action:
            raise InternalServerError(
                f"intent next_action is required but missing ({payment_intent.next_action})"
            )
        return create_action_required_response(transaction, payment_intent)

    elif payment_intent.status == PaymentIntentStatus.REQUIRES_CONFIRMATION:
        confirmed_intent = stripe.PaymentIntent.confirm(payment_intent.id)
        assert confirmed_intent.status != PaymentIntentStatus.REQUIRES_CONFIRMATION
        return create_client_response(transaction, confirmed_intent)

    elif payment_intent.status == PaymentIntentStatus.SUCCEEDED:
        payment_success(transaction)
        logger.info(
            f"succeeded: payment for transaction {transaction.id}, payment_intent id {payment_intent.id}"
        )
        return None

    elif payment_intent.status == PaymentIntentStatus.REQUIRES_PAYMENT_METHOD:
        commit_fail_transaction(transaction)
        logger.info(
            f"failed: payment for transaction {transaction.id}, payment_intent id {payment_intent.id}"
        )
        raise BadRequest(
            log=
            f"payment_intent requires payment method, either no method provided or the payment failed"
        )

    else:
        raise InternalServerError(
            log=
            f"unexpected stripe payment_intent status {payment_intent.status}, this is a bug"
        )
Esempio n. 10
0
def password_reset(reset_token, unhashed_password):
    try:
        password_reset_token = db_session.query(PasswordResetToken).filter_by(
            token=reset_token).one()

    except NoResultFound:
        return dict(
            error_message=
            "Could not find password reset token, try to request a new reset link."
        )

    except MultipleResultsFound:
        raise InternalServerError(
            log=f"Multiple tokens {reset_token} found, this is a bug.")

    if datetime.utcnow() - password_reset_token.created_at > timedelta(
            minutes=10):
        return dict(error_message="Reset link expired, try to request a new.")

    try:
        hashed_password = check_and_hash_password(unhashed_password)
    except ValueError as e:
        return dict(error_message=str(e))

    try:
        member = db_session.query(Member).get(password_reset_token.member_id)
    except NoResultFound:
        raise InternalServerError(
            log=
            f"No member with id {password_reset_token.member_id} found, this is a bug."
        )

    member.password = hashed_password
    db_session.add(member)

    return {}
Esempio n. 11
0
def capture_stripe_payment_intent(transaction, payment_intent):
    """ This is payment_intent is authorized and can be captured synchronously. """

    if transaction.status != Transaction.PENDING:
        raise InternalServerError(
            f"unexpected status of transaction",
            log=
            f"transaction {transaction.id} has unexpected status {transaction.status}"
        )

    try:
        captured_intent = stripe.PaymentIntent.capture(payment_intent.id)

        complete_payment_intent_transaction(transaction, captured_intent)
    except InvalidRequestError as e:
        raise PaymentFailed(
            log=f"stripe capture payment_intent failed: {str(e)}",
            level=EXCEPTION)

    except StripeError as e:
        raise InternalServerError(
            log=
            f"stripe capture payment_intent failed (possibly temporarily): {str(e)}"
        )
Esempio n. 12
0
 def create(self, data=None, commit=True):
     if data is None:
         data = request.json or {}
     
     status, = db_session.execute("SELECT GET_LOCK('display_order', 20)").fetchone()
     if not status:
         raise InternalServerError("Failed to create, try again later.",
                                   log="failed to aquire display_order lock")
     try:
         if data.get('display_order') is None:
             data['display_order'] = (db_session.query(func.max(self.model.display_order)).scalar() or 0) + 1
         obj = self.to_obj(self._create_internal(data, commit=commit))
         return obj
     except Exception:
         # Rollback session if anything went wrong or we can't release the lock.
         db_session.rollback()
         raise
     finally:
         db_session.execute("DO RELEASE_LOCK('display_order')")
Esempio n. 13
0
def stripe_source_event(subtype, event):
    source = event.data.object

    transaction = get_pending_source_transaction(source.id)

    if subtype == Subtype.CHARGEABLE:

        if source.type == SourceType.THREE_D_SECURE:
            # Charge card and resolve transaction, don't fail transaction on errors as it may be resolved when we get
            # callback again.
            try:
                charge = create_stripe_charge(transaction, source.id)
            except PaymentFailed as e:
                logger.info(
                    f"failing transaction {transaction.id}, permanent error when creating charge: {str(e)}"
                )
                commit_fail_transaction(transaction)
            else:
                charge_transaction(transaction, charge)

        elif source.type == SourceType.CARD:
            # Non 3d secure cards should be charged synchronously in payment, not here.
            raise IgnoreEvent(
                f"transaction {transaction.id} source event of type card is handled synchronously"
            )

        else:
            raise InternalServerError(
                log=f"unexpected source type '{source.type}'"
                f" when handling source event: {source}")

    elif subtype in (Subtype.FAILED, Subtype.CANCELED):
        logger.info(
            f"failing transaction {transaction.id} due to source event subtype {subtype}"
        )
        commit_fail_transaction(transaction)

    else:
        raise IgnoreEvent(
            f"source event subtype {subtype} for transaction {transaction.id}")