def create_fake_tickets(self, user): if random.random() < 0.3: return if random.random() < 0.2: currency = "EUR" else: currency = "GBP" b = Basket(user, currency) pt = PriceTier.query.filter_by(name="full-std").one() b[pt] = int(round(random.uniform(1, 4))) if random.random() < 0.5: pt = PriceTier.query.filter_by(name="parking").one() b[pt] = 1 b.create_purchases() b.ensure_purchase_capacity() payment_type = { "gc": GoCardlessPayment, "bank": BankPayment, "stripe": StripePayment, }.get(random_state({"gc": 0.3, "bank": 0.2, "stripe": 0.5})) payment = b.create_payment(payment_type) if random.random() < 0.8: payment.paid() else: payment.cancel()
def create_fixtures(self): self.user1_email = self.user1_email_template.format(random_string(8)) self.user2_email = self.user2_email_template.format(random_string(8)) self.user1 = User(self.user1_email, 'test_user1') self.user2 = User(self.user2_email, 'test_user2') self.db.session.add(self.user1) self.db.session.add(self.user2) self.group_name = self.group_template.format(random_string(8)) self.group = ProductGroup(type='admissions', name=self.group_name) self.product = Product(name=self.product_name, parent=self.group) self.tier = PriceTier(name=self.tier_name, parent=self.product) self.price = Price(price_tier=self.tier, currency='GBP', price_int=666) self.db.session.add(self.price) self.db.session.commit() # PriceTier needs to have been committed before this basket = Basket(self.user1, 'GBP') basket[self.tier] = 1 basket.create_purchases() basket.ensure_purchase_capacity() payment = basket.create_payment(BankPayment) assert len(payment.purchases) == 1 self.db.session.commit()
def create_purchases(self, tier, count): basket = Basket(self.user, 'GBP') basket[tier] = count basket.create_purchases() basket.ensure_purchase_capacity() payment = basket.create_payment(BankPayment) assert len(payment.purchases) == count self.db.session.commit() return payment.purchases
def create_purchases(tier, count, user): basket = Basket(user, "GBP") basket[tier] = count basket.create_purchases() basket.ensure_purchase_capacity() payment = basket.create_payment(BankPayment) db.session.add(payment) db.session.commit() assert len(payment.purchases) == count return payment.purchases
def create_purchases(tier, count, user): basket = Basket(user, 'GBP') basket[tier] = count basket.create_purchases() basket.ensure_purchase_capacity() payment = basket.create_payment(BankPayment) db.session.add(payment) db.session.commit() assert len(payment.purchases) == count return payment.purchases
def create_fake_tickets(self, user): if random.random() < 0.3: return if random.random() < 0.2: currency = 'EUR' else: currency = 'GBP' b = Basket(user, currency) pt = PriceTier.query.filter_by(name='full-std').one() b[pt] = int(round(random.uniform(1, 4))) if random.random() < 0.5: pt = PriceTier.query.filter_by(name='parking').one() b[pt] = 1 b.create_purchases() b.ensure_purchase_capacity() payment_type = { 'gc': GoCardlessPayment, 'bank': BankPayment, 'stripe': StripePayment }.get(random_state({ 'gc': 0.3, 'bank': 0.2, 'stripe': 0.5 })) payment = b.create_payment(payment_type) if random.random() < 0.8: payment.paid() else: payment.cancel()
def start_payment(form: TicketPaymentForm, basket: Basket, flow: str): if Decimal(form.basket_total.data) != Decimal(basket.total): # Check that the user's basket approximately matches what we told them they were paying. price_changed.inc() app.logger.warn( "User's basket has changed value %s -> %s", form.basket_total.data, basket.total, ) flash( """The items you selected have changed, possibly because you had two windows open. Please verify that you've selected the correct items.""" ) return redirect(url_for("tickets.pay", flow=flow)) user = current_user if user.is_anonymous: try: new_user = create_current_user(form.email.data, form.name.data) except IntegrityError as e: app.logger.warn("Adding user raised %r, possible double-click", e) return redirect(url_for("tickets.pay", flow=flow)) user = new_user elif user.name == user.email: user.name = form.name.data if form.allow_promo.data: user.promo_opt_in = True if basket.requires_shipping: if not user.shipping: user.shipping = UserShipping() user.shipping.address_1 = form.address_1.data user.shipping.address_2 = form.address_2.data user.shipping.town = form.town.data user.shipping.postcode = form.postcode.data user.shipping.country = form.country.data payment_type = form.get_payment_class() basket.user = user try: payment = basket.create_payment(payment_type) except VoucherUsedError as e: # Voucher has been used since we last checked it at the "choose" stage. app.logger.exception("Voucher used at payment stage") flash( "The voucher you've used does not allow you to buy this many adult tickets. " "Please choose fewer tickets." ) db.session.rollback() return redirect(url_for("tickets.main", flow=flow)) basket.cancel_surplus_purchases() db.session.commit() # Remove voucher ID from session, if it exists. try: del session["ticket_voucher"] except KeyError: pass Basket.clear_from_session() if not payment: empty_baskets.inc() app.logger.warn("User tried to pay for empty basket") flash( "We're sorry, your session information has been lost. Please try ordering again." ) return redirect(url_for("tickets.main", flow=flow)) if payment_type == BankPayment: return transfer_start(payment) if payment_type == StripePayment: return stripe_start(payment) app.logger.exception(f"Unexpected payment_type: {repr(payment_type)}") flash("We're sorry, an unexpected error occurred. Please try ordering again.") return redirect(url_for("tickets.main", flow=flow))
def test_create_stripe_purchase(user, app, monkeypatch): # Add some tickets to a basket (/tickets/choose) basket = Basket(user, "GBP") tier = PriceTier.query.filter_by(name="full-std").one_or_none() basket[tier] = 2 basket.create_purchases() basket.ensure_purchase_capacity() db.session.commit() # This matches the intent ID in stored fixtures intent_id = "pi_1GUslpIcI91cWsdeheAuRsyg" with app.test_request_context("/tickets/pay"): login_user(user) payment = basket.create_payment(StripePayment) stripe_start(payment) assert payment.state == "new" with app.test_request_context(f"/pay/stripe/{payment.id}/capture"): login_user(user) # Start capture process - this creates a payment intent from fake-stripe stripe_capture(payment.id) # A payment_intent.created webhook should be generated here, but it # doesn't cause any action on our end so we don't simulate this. assert payment.intent_id == intent_id assert payment.state == "new" # User is now on the Stripe form, which captures the card details. # Once this is complete, payment details are sent to Stripe and the form # submission triggers stripe_capture_post stripe_capture_post(payment.id) assert payment.state == "charging" with app.test_request_context("/stripe-webhook"): # Stripe will now send a webhook to notify us of the payment success. stripe_payment_intent_updated( "payment_intent.succeeded", load_webhook_fixture("payment_intent.succeeded")) # A charge.succeeded webhook is also sent but we ignore it. assert payment.state == "paid" assert all( purchase.state == "paid" for purchase in payment.purchases), "Purchases should be marked as paid after payment" # Payment is all paid. Now we test refunding it. # Create a refund request for the entire payment, with £20 donation. refund_request = RefundRequest(payment=payment, donation=20, currency=payment.currency) payment.state = "refund-requested" db.session.add(refund_request) db.session.commit() handle_refund_request(refund_request) with app.test_request_context("/stripe-webhook"): # charge.refunded webhook. We do process this but currently we don't use it for anything. stripe_charge_refunded("charge.refunded", load_webhook_fixture("charge.refunded")) # Payment should be marked as fully refunded. assert payment.state == "refunded" assert all(purchase.state == "refunded" for purchase in payment.purchases ), "Purchases should be marked as refunded after refund"