Exemplo n.º 1
0
def create_customer(
    subhub_account: SubHubAccount,
    user_id: str,
    email: str,
    source_token: str,
    origin_system: str,
    display_name: str,
) -> Customer:
    _validate_origin_system(origin_system)
    # First search Stripe to ensure we don't have an unlinked Stripe record
    # already in Stripe
    customer = None
    customers = vendor.get_customer_list(email=email)
    for possible_customer in customers.data:
        if possible_customer.email == email:
            # If the userid doesn't match, the system is damaged.
            if possible_customer.metadata.get("userid") != user_id:
                customer_message = "customer email exists but userid mismatch"
                raise ServerError(customer_message)

            customer = possible_customer
            # If we have a mis-match on the source_token, overwrite with the
            # new one.
            if customer.default_source != source_token:
                vendor.modify_customer(
                    customer_id=customer.id,
                    source_token=source_token,
                    idempotency_key=utils.get_indempotency_key(),
                )
            break

    # No existing Stripe customer, create one.
    if not customer:
        customer = vendor.create_stripe_customer(
            source_token=source_token,
            email=email,
            userid=user_id,
            name=display_name,
            idempotency_key=utils.get_indempotency_key(),
        )
    # Link the Stripe customer to the origin system id
    db_account = subhub_account.new_user(uid=user_id,
                                         origin_system=origin_system,
                                         cust_id=customer.id)

    try:
        new_user = subhub_account.save_user(db_account)
        if not new_user:
            # Clean-up the Stripe customer record since we can't link it
            vendor.delete_stripe_customer(customer_id=customer.id)
    except IntermittentError("error saving db record") as e:  # type: ignore
        logger.error("unable to save user or link it", error=e)
        raise e
    return customer
Exemplo n.º 2
0
def existing_payment_source(existing_customer: Customer,
                            source_token: str) -> Customer:
    """
    If Customer does not have an existing Payment Source and has not been Deleted:
        - Set the Customer's payment source to the new token
        - return the updated Customer
    Else
        - return the provided customer
    :param existing_customer:
    :param source_token:
    :return:
    """
    if not existing_customer.get("sources"):
        if not existing_customer.get("deleted"):
            existing_customer = vendor.modify_customer(
                customer_id=existing_customer["id"],
                source_token=source_token,
                idempotency_key=utils.get_indempotency_key(),
            )
            logger.info("add source", existing_customer=existing_customer)
        else:
            logger.info(
                "stripe customer is marked as deleted. cannot add source.")
    logger.debug("existing payment source",
                 existing_customer=existing_customer)
    return existing_customer
Exemplo n.º 3
0
def subscribe_to_plan(uid: str, data: Dict[str, Any]) -> FlaskResponse:
    """
    Subscribe to a plan given a user id, payment token, email, orig_system
    :param uid:
    :param data:
    :return: current subscriptions for user.
    """
    customer = existing_or_new_customer(
        g.subhub_account,
        user_id=uid,
        email=data["email"],
        source_token=data["pmt_token"],
        origin_system=data["origin_system"],
        display_name=data["display_name"],
    )
    existing_plan = has_existing_plan(customer, plan_id=data["plan_id"])
    if existing_plan:
        return dict(message="User already subscribed."), 409

    if not customer.get("deleted"):
        vendor.build_stripe_subscription(customer.id, data["plan_id"],
                                         utils.get_indempotency_key())
        updated_customer = fetch_customer(g.subhub_account, user_id=uid)
        newest_subscription = find_newest_subscription(
            updated_customer["subscriptions"])
        return create_return_data(newest_subscription), 201

    return dict(message=None), 400
Exemplo n.º 4
0
    def test_cancel_immediately_error(self):
        self.mock_delete_subscription.side_effect = APIError("message")

        with self.assertRaises(APIError):
            vendor.cancel_stripe_subscription_immediately(
                subscription_id="sub_123", idempotency_key=utils.get_indempotency_key()
            )
Exemplo n.º 5
0
    def test_reactivate_error(self):
        self.mock_modify_subscription.side_effect = APIError("message")

        with self.assertRaises(APIError):
            vendor.reactivate_stripe_subscription(
                subscription_id="sub_123", idempotency_key=utils.get_indempotency_key()
            )
Exemplo n.º 6
0
    def test_cancel_at_end_error(self):
        self.mock_modify_subscription.side_effect = APIError("message")

        with self.assertRaises(APIError):
            vendor.cancel_stripe_subscription_period_end(
                subscription_id="sub_123", idempotency_key=utils.get_indempotency_key()
            )
Exemplo n.º 7
0
def cancel_subscription(uid: str, sub_id: str) -> FlaskResponse:
    """
    Cancel an existing subscription for a user.
    :param uid:
    :param sub_id:
    :return: Success or failure message for the cancellation.
    """
    customer = fetch_customer(g.subhub_account, uid)
    if not customer:
        return dict(message="Customer does not exist."), 404

    for item in customer["subscriptions"]["data"]:
        if item["id"] == sub_id and item["status"] in [
                "active",
                "trialing",
                "incomplete",
        ]:
            vendor.cancel_stripe_subscription_period_end(
                sub_id, utils.get_indempotency_key())
            updated_customer = fetch_customer(g.subhub_account, uid)
            logger.info("updated customer", updated_customer=updated_customer)
            subs = retrieve_stripe_subscriptions(updated_customer)
            logger.info("subs", subs=subs, type=type(subs))
            for sub in subs:
                if sub["cancel_at_period_end"] and sub["id"] == sub_id:
                    return {
                        "message": "Subscription cancellation successful"
                    }, 201

    return dict(message="Subscription not available."), 400
Exemplo n.º 8
0
def create_customer(
    subhub_account: SubHubAccount,
    user_id: str,
    email: str,
    source_token: str,
    origin_system: str,
    display_name: str,
) -> Customer:
    _validate_origin_system(origin_system)
    # First search Stripe to ensure we don't have an unlinked Stripe record
    # already in Stripe
    customer = search_customers(email=email, user_id=user_id)

    # If we have a mis-match on the source_token, overwrite with the
    # new one.
    if customer is not None and customer.default_source != source_token:
        customer = vendor.modify_customer(
            customer_id=customer.id,
            source_token=source_token,
            idempotency_key=utils.get_indempotency_key(),
        )

    # No existing Stripe customer, create one.
    if not customer:
        customer = vendor.create_stripe_customer(
            source_token=source_token,
            email=email,
            userid=user_id,
            name=display_name,
            idempotency_key=utils.get_indempotency_key(),
        )
    # Link the Stripe customer to the origin system id
    db_account = subhub_account.new_user(uid=user_id,
                                         origin_system=origin_system,
                                         cust_id=customer.id)

    new_user = subhub_account.save_user(db_account)
    if not new_user:
        # Clean-up the Stripe customer record since we can't link it
        vendor.delete_stripe_customer(customer_id=customer.id)
        logger.error("unable to save user or link it")
        raise IntermittentError("error saving db record")

    logger.debug("create customer", customer=customer)
    return customer
Exemplo n.º 9
0
    def test_build_error(self):
        self.mock_create_subscription.side_effect = APIError("message")

        with self.assertRaises(APIError):
            vendor.build_stripe_subscription(
                customer_id="cust_123",
                plan_id="plan_123",
                idempotency_key=utils.get_indempotency_key(),
            )
Exemplo n.º 10
0
    def test_modify_error(self):
        self.modify_customer_mock.side_effect = APIError("message")

        with self.assertRaises(APIError):
            vendor.modify_customer(  # nosec
                customer_id="cust_123",
                source_token="token",
                idempotency_key=utils.get_indempotency_key(),
            )
Exemplo n.º 11
0
    def test_update_error(self):
        self.mock_modify_subscription.side_effect = APIError("message")

        with self.assertRaises(APIError):
            vendor.update_stripe_subscription(
                subscription=self.subscription,
                plan_id="plan_123",
                idempotency_key=utils.get_indempotency_key(),
            )
Exemplo n.º 12
0
    def test_modify_success(self):
        self.modify_customer_mock.side_effect = [APIError("message"), self.customer]

        customer = vendor.modify_customer(  # nosec
            customer_id="cust_123",
            source_token="token",
            idempotency_key=utils.get_indempotency_key(),
        )

        assert customer == self.customer  # nosec
Exemplo n.º 13
0
    def test_cancel_immediately_success(self):
        self.mock_delete_subscription.side_effect = [
            APIError("message"),
            self.subscription,
        ]

        subscription = vendor.cancel_stripe_subscription_immediately(
            subscription_id="sub_123", idempotency_key=utils.get_indempotency_key()
        )

        assert subscription == self.subscription  # nosec
Exemplo n.º 14
0
    def test_reactivate_success(self):
        self.mock_modify_subscription.side_effect = [
            APIError("message"),
            self.subscription,
        ]

        subscription = vendor.reactivate_stripe_subscription(
            subscription_id="sub_123", idempotency_key=utils.get_indempotency_key()
        )

        assert subscription == self.subscription  # nosec
Exemplo n.º 15
0
def test_create_stripe_customer_error():
    disable_base()
    with pytest.raises(APIError):
        vendor.create_stripe_customer(
            "token",
            "*****@*****.**",
            "abc123",
            "Anonymous",
            utils.get_indempotency_key(),
        )
    enable_base()
Exemplo n.º 16
0
    def test_create_error(self):
        self.create_customer_mock.side_effect = APIError("message")

        with self.assertRaises(APIError):
            vendor.create_stripe_customer(  # nosec
                source_token="token",
                email="*****@*****.**",
                userid="user_123",
                name="Test User",
                idempotency_key=utils.get_indempotency_key(),
            )
Exemplo n.º 17
0
    def test_cancel_at_end_success(self):
        self.mock_modify_subscription.side_effect = [
            APIError("message"),
            self.subscription,
        ]

        subscription = vendor.cancel_stripe_subscription_period_end(
            subscription_id="sub_123", idempotency_key=utils.get_indempotency_key()
        )

        assert subscription == self.subscription  # nosec
Exemplo n.º 18
0
    def test_create_success(self):
        self.create_customer_mock.side_effect = [APIError("message"), self.customer]

        customer = vendor.create_stripe_customer(  # nosec
            source_token="token",
            email="*****@*****.**",
            userid="user_123",
            name="Test User",
            idempotency_key=utils.get_indempotency_key(),
        )

        assert customer == self.customer  # nosec
Exemplo n.º 19
0
def subscribe_customer(customer: Customer, plan_id: str) -> Subscription:
    """
    Subscribe Customer to Plan
    :param customer:
    :param plan_id:
    :return: Subscription Object
    """
    sub = vendor.build_stripe_subscription(
        customer.id,
        plan_id=plan_id,
        idempotency_key=utils.get_indempotency_key())
    return sub
Exemplo n.º 20
0
def existing_payment_source(existing_customer: Customer,
                            source_token: str) -> Customer:
    if not existing_customer.get("sources"):
        if not existing_customer.get("deleted"):
            existing_customer = vendor.modify_customer(
                customer_id=existing_customer["id"],
                source_token=source_token,
                idempotency_key=utils.get_indempotency_key(),
            )
            logger.info("add source", existing_customer=existing_customer)
        else:
            logger.info("existing source deleted")
    return existing_customer
Exemplo n.º 21
0
    def test_build_success(self):
        self.mock_create_subscription.side_effect = [
            APIError("message"),
            self.subscription,
        ]

        subscription = vendor.build_stripe_subscription(
            customer_id="cust_123",
            plan_id="plan_123",
            idempotency_key=utils.get_indempotency_key(),
        )

        assert subscription == self.subscription  # nosec
Exemplo n.º 22
0
def reactivate_subscription(uid: str, sub_id: str) -> FlaskResponse:
    """
    Given a user's subscription that is flagged for cancellation, but is still active
    remove the cancellation flag to ensure the subscription remains active
    :param uid: User ID
    :param sub_id: Subscription ID
    :return: Success or failure message for the activation
    """

    customer = fetch_customer(g.subhub_account, uid)
    if not customer:
        response_message = dict(message="Customer does not exist.")
        logger.debug("reactivate subscription",
                     response_message=response_message)
        return response_message, 404

    active_subscriptions = customer["subscriptions"]["data"]
    response_message = dict(message="Current subscription not found.")
    for subscription in active_subscriptions:
        if subscription["id"] == sub_id:
            response_message = dict(message="Subscription is already active.")
            if subscription["cancel_at_period_end"]:
                reactivate_stripe_subscription(sub_id,
                                               utils.get_indempotency_key())
                response_message = dict(
                    message="Subscription reactivation was successful.")
                logger.debug(
                    "reactivate subscription",
                    response_message=response_message,
                    response_code=200,
                )
                return response_message, 200
            logger.debug(
                "reactivate subscription",
                response_message=response_message,
                response_code=200,
            )
            return response_message, 200
    logger.debug("reactivate subscription",
                 response_message=response_message,
                 response_code=404)
    return response_message, 404
Exemplo n.º 23
0
def update_payment_method(uid, data) -> FlaskResponse:
    """
    Given a user id and a payment token, update user's payment method
    :param uid:
    :param data:
    :return: Success or failure message.
    """
    customer = fetch_customer(g.subhub_account, uid)
    logger.debug("customer", customer=customer)
    if not customer:
        response_message = dict(message="Customer does not exist.")
        logger.debug(
            "update payment method",
            response_message=response_message,
            response_code=404,
        )
        return response_message, 404

    metadata = customer.get("metadata")
    logger.debug("metadata", metadata=metadata, customer=type(customer))
    if metadata:
        if metadata.get("userid", None) == uid:
            modify_customer(
                customer_id=customer.id,
                source_token=data["pmt_token"],
                idempotency_key=utils.get_indempotency_key(),
            )
            response_message = dict(
                message="Payment method updated successfully.")
            logger.debug(
                "update payment method",
                response_message=response_message,
                response_code=201,
            )
            return response_message, 201
    response_message = dict(message="Customer mismatch.")
    logger.debug("update payment method",
                 response_message=response_message,
                 response_code=400)
    return response_message, 400
Exemplo n.º 24
0
def update_subscription(uid: str, sub_id: str,
                        data: Dict[str, Any]) -> FlaskResponse:
    """
    Update a Customer's Subscription with a new Plan
    Locate a Stripe Customer from the provided uid and locate the Customer's subscription from the provided sub_id
        - If the Customer is not found, or the Customer object does not contain a Subscription with the sub_id
            :return 404 Not Found
    Determine if the new plan_id can replace the current Subscription Plan:
        - If the new plan_id and current plan_id are the same
            : return 400 Bad Request
        - If the new plan and the old plan have different intervals:
            : return 400 Bad Request
        - If the products do not have the same ProductSet metadata
            :return 400 Bad Request
    Make call to Stripe to update the Subscription
        :return 200 OK - Updated Subscription in response body

    :param uid:
    :param sub_id:
    :param data:
    :return:
    """
    customer = find_customer(g.subhub_account, uid)
    subscription = find_customer_subscription(customer, sub_id)

    current_plan = subscription["plan"]
    new_plan_id = data["plan_id"]

    new_product = validate_plan_change(current_plan, new_plan_id)

    updated_subscription = update_stripe_subscription(
        subscription, new_plan_id, utils.get_indempotency_key())

    formatted_subscription = format_subscription(
        convert_to_dict(updated_subscription), new_product)

    return formatted_subscription, 200
Exemplo n.º 25
0
def delete_customer(uid: str) -> FlaskResponse:
    """
    Delete an existing customer, cancel active subscriptions
    and delete from payment provider
    :param uid:
    :return: Success of failure message for the deletion
    """
    logger.info("delete customer", uid=uid)
    subscription_user = g.subhub_account.get_user(uid)
    logger.info("delete customer", subscription_user=subscription_user)
    if subscription_user is not None:
        origin = subscription_user.origin_system
        logger.info("delete origin", origin=origin)
        if not subscription_user:
            return dict(message="Customer does not exist."), 404
        subscribed_customer = vendor.retrieve_stripe_customer(
            subscription_user.cust_id)
        subscribed_customer = subscribed_customer.to_dict()
        subscription_info: List = []
        logger.info(
            "subscribed customer",
            subscribed_customer=subscribed_customer,
            data_type=type(subscribed_customer),
        )

        products = {}  # type: Dict
        for subs in subscribed_customer["subscriptions"]["data"]:
            try:
                product = products[subs.plan.product]
            except KeyError:
                product = Product.retrieve(subs.plan.product)
                products[subs.plan.product] = product
            plan_id = subs.plan.product

            sub = dict(
                plan_amount=subs.plan.amount,
                nickname=format_plan_nickname(subs.plan.nickname,
                                              subs.plan.interval),
                productId=plan_id,
                current_period_end=subs.current_period_end,
                current_period_start=subs.current_period_start,
                subscription_id=subs.id,
            )
            subscription_info.append(sub)
            vendor.cancel_stripe_subscription_immediately(
                subs.id, utils.get_indempotency_key())
            data = dict(
                uid=subscribed_customer["metadata"]["userid"],
                active=False,
                subscriptionId=subs.id,
                productId=plan_id,
                eventId=utils.get_indempotency_key(),
                eventCreatedAt=int(time.time()),
                messageCreatedAt=int(time.time()),
            )
            sns_message = Message(json.dumps(data)).route()
            logger.info("delete message", sns_message=sns_message)
        else:
            deleted_payment_customer = vendor.delete_stripe_customer(
                subscription_user.cust_id)
            if deleted_payment_customer:
                deleted_customer = delete_user(
                    user_id=subscribed_customer["metadata"]["userid"],
                    cust_id=subscribed_customer["id"],
                    origin_system=origin,
                    subscription_info=subscription_info,
                )
                user = g.subhub_account.get_user(uid)
                if deleted_customer and user is None:
                    return dict(message="Customer deleted successfully"), 200
    return dict(message="Customer not available"), 400
Exemplo n.º 26
0
def test_cancel_stripe_subscription_period_end_error():
    disable_base()
    with pytest.raises(APIError):
        vendor.cancel_stripe_subscription_period_end(
            "no_sub", utils.get_indempotency_key())
    enable_base()
Exemplo n.º 27
0
def test_build_stripe_subscription_error():
    disable_base()
    with pytest.raises(APIError):
        vendor.build_stripe_subscription("no_one", "no_plan",
                                         utils.get_indempotency_key())
    enable_base()
Exemplo n.º 28
0
def test_modify_customer_error():
    disable_base()
    with pytest.raises(APIError):
        vendor.modify_customer("no_customer", "tok_nothing",
                               utils.get_indempotency_key())
    enable_base()