def test_refund_success(env, factory, monkeypatch): event, order = env def charge_retr(*args, **kwargs): def refund_create(amount): r = MockedCharge() r.id = 'foo' r.status = 'succeeded' return r c = MockedCharge() c.refunds.create = refund_create return c monkeypatch.setattr("stripe.Charge.retrieve", charge_retr) order.status = Order.STATUS_PAID p = order.payments.create(provider='stripe_cc', amount=order.total, info=json.dumps({ 'id': 'ch_123345345' })) order.save() prov = StripeCC(event) refund = order.refunds.create( provider='stripe_cc', amount=order.total, payment=p, ) prov.execute_refund(refund) refund.refresh_from_db() assert refund.state == OrderRefund.REFUND_STATE_DONE
def test_refund_unavailable(env, factory, monkeypatch): event, order = env def charge_retr(*args, **kwargs): def refund_create(amount): raise APIConnectionError(message='Foo') c = MockedCharge() c.refunds.create = refund_create return c monkeypatch.setattr("stripe.Charge.retrieve", charge_retr) order.status = Order.STATUS_PAID p = order.payments.create(provider='stripe_cc', amount=order.total, info=json.dumps({'id': 'ch_123345345'})) order.save() prov = StripeCC(event) refund = order.refunds.create(provider='stripe_cc', amount=order.total, payment=p) with pytest.raises(PaymentException): prov.execute_refund(refund) refund.refresh_from_db() assert refund.state != OrderRefund.REFUND_STATE_DONE
def test_refund_success(env, factory, monkeypatch): event, order = env def charge_retr(*args, **kwargs): def refund_create(amount): r = MockedCharge() r.id = 'foo' r.status = 'succeeded' return r c = MockedCharge() c.refunds.create = refund_create return c monkeypatch.setattr("stripe.Charge.retrieve", charge_retr) order.status = Order.STATUS_PAID p = order.payments.create(provider='stripe_cc', amount=order.total, info=json.dumps({'id': 'ch_123345345'})) order.save() prov = StripeCC(event) refund = order.refunds.create( provider='stripe_cc', amount=order.total, payment=p, ) prov.execute_refund(refund) refund.refresh_from_db() assert refund.state == OrderRefund.REFUND_STATE_DONE
def test_perform_success_zero_decimal_currency(env, factory, monkeypatch): event, order = env event.currency = 'JPY' event.save() def charge_create(**kwargs): assert kwargs['amount'] == 13 assert kwargs['currency'] == 'jpy' assert kwargs['source'] == 'tok_189fTT2eZvKYlo2CvJKzEzeu' c = MockedCharge() c.status = 'succeeded' c.paid = True return c monkeypatch.setattr("stripe.Charge.create", charge_create) prov = StripeCC(event) req = factory.post('/', { 'stripe_token': 'tok_189fTT2eZvKYlo2CvJKzEzeu', 'stripe_last4': '4242', 'stripe_brand': 'Visa' }) req.session = {} prov.checkout_prepare(req, {}) assert 'payment_stripe_token' in req.session payment = order.payments.create( provider='stripe_cc', amount=order.total ) prov.execute_payment(req, payment) order.refresh_from_db() assert order.status == Order.STATUS_PAID
def test_refund_unavailable(env, factory, monkeypatch): event, order = env def charge_retr(*args, **kwargs): def refund_create(amount): raise APIConnectionError(message='Foo') c = MockedCharge() c.refunds.create = refund_create return c monkeypatch.setattr("stripe.Charge.retrieve", charge_retr) order.status = Order.STATUS_PAID p = order.payments.create(provider='stripe_cc', amount=order.total, info=json.dumps({ 'id': 'ch_123345345' })) order.save() prov = StripeCC(event) refund = order.refunds.create( provider='stripe_cc', amount=order.total, payment=p ) with pytest.raises(PaymentException): prov.execute_refund(refund) refund.refresh_from_db() assert refund.state != OrderRefund.REFUND_STATE_DONE
def test_perform_failed(env, factory, monkeypatch): event, order = env def charge_create(**kwargs): c = MockedCharge() c.status = 'failed' c.paid = True c.failure_message = 'Foo' return c monkeypatch.setattr("stripe.Charge.create", charge_create) prov = StripeCC(event) req = factory.post('/', { 'stripe_token': 'tok_189fTT2eZvKYlo2CvJKzEzeu', 'stripe_last4': '4242', 'stripe_brand': 'Visa' }) req.session = {} prov.checkout_prepare(req, {}) assert 'payment_stripe_token' in req.session with pytest.raises(PaymentException): payment = order.payments.create( provider='stripe_cc', amount=order.total ) prov.execute_payment(req, payment) order.refresh_from_db() assert order.status == Order.STATUS_PENDING
def stripe_verify_domain(event, domain): from pretix.plugins.stripe.payment import StripeCC prov = StripeCC(event) account = get_stripe_account_key(prov) # Yes, we could just use the **prov.api_kwargs # But since we absolutely need to always issue this call with live keys, # we're building our api_kwargs here by hand. # Only if no live connect secret key is set, we'll fall back to the testmode keys. # But this should never happen except in scenarios where pretix runs in devmode. if prov.settings.connect_client_id and prov.settings.connect_user_id: api_kwargs = { 'api_key': prov.settings.connect_secret_key or prov.settings.connect_test_secret_key, 'stripe_account': prov.settings.connect_user_id } else: api_kwargs = { 'api_key': prov.settings.secret_key, } if RegisteredApplePayDomain.objects.filter(account=account, domain=domain).exists(): return try: resp = stripe.ApplePayDomain.create(domain_name=domain, **api_kwargs) except stripe.error.StripeError: logger.exception('Could not verify domain with Stripe') else: if resp.livemode: RegisteredApplePayDomain.objects.create(domain=domain, account=account)
def paymentintent_webhook(event, event_json, paymentintent_id, rso): prov = StripeCC(event) prov._init_api() try: paymentintent = stripe.PaymentIntent.retrieve(paymentintent_id, **prov.api_kwargs) except stripe.error.StripeError: logger.exception('Stripe error on webhook. Event data: %s' % str(event_json)) return HttpResponse('Charge not found', status=500) for charge in paymentintent.charges.data: ReferencedStripeObject.objects.get_or_create( reference=charge.id, defaults={'order': rso.payment.order, 'payment': rso.payment} ) return HttpResponse(status=200)
def test_perform_failed(env, factory, monkeypatch): event, order = env def charge_create(**kwargs): c = MockedCharge() c.status = 'failed' c.paid = True c.failure_message = 'Foo' return c monkeypatch.setattr("stripe.Charge.create", charge_create) prov = StripeCC(event) req = factory.post( '/', { 'stripe_token': 'tok_189fTT2eZvKYlo2CvJKzEzeu', 'stripe_last4': '4242', 'stripe_brand': 'Visa' }) req.session = {} prov.checkout_prepare(req, {}) assert 'payment_stripe_token' in req.session with pytest.raises(PaymentException): prov.payment_perform(req, order) order.refresh_from_db() assert order.status == Order.STATUS_PENDING
def test_perform_success(env, factory, monkeypatch): event, order = env def charge_create(**kwargs): assert kwargs['amount'] == 1337 assert kwargs['currency'] == 'eur' assert kwargs['source'] == 'tok_189fTT2eZvKYlo2CvJKzEzeu' c = MockedCharge() c.status = 'succeeded' c.paid = True return c monkeypatch.setattr("stripe.Charge.create", charge_create) prov = StripeCC(event) req = factory.post('/', { 'stripe_token': 'tok_189fTT2eZvKYlo2CvJKzEzeu', 'stripe_last4': '4242', 'stripe_brand': 'Visa' }) req.session = {} prov.checkout_prepare(req, {}) assert 'payment_stripe_token' in req.session payment = order.payments.create( provider='stripe_cc', amount=order.total ) prov.execute_payment(req, payment) order.refresh_from_db() assert order.status == Order.STATUS_PAID
def test_perform_success_zero_decimal_currency(env, factory, monkeypatch): event, order = env event.currency = 'JPY' event.save() def paymentintent_create(**kwargs): assert kwargs['amount'] == 13 assert kwargs['currency'] == 'jpy' assert kwargs['payment_method'] == 'pm_189fTT2eZvKYlo2CvJKzEzeu' c = MockedPaymentintent() c.status = 'succeeded' c.charges.data[0].paid = True return c monkeypatch.setattr("stripe.PaymentIntent.create", paymentintent_create) prov = StripeCC(event) req = factory.post( '/', { 'stripe_payment_method_id': 'pm_189fTT2eZvKYlo2CvJKzEzeu', 'stripe_last4': '4242', 'stripe_brand': 'Visa' }) req.session = {} prov.checkout_prepare(req, {}) assert 'payment_stripe_payment_method_id' in req.session payment = order.payments.create(provider='stripe_cc', amount=order.total) prov.execute_payment(req, payment) order.refresh_from_db() assert order.status == Order.STATUS_PAID
def test_perform_success_zero_decimal_currency(env, factory, monkeypatch): event, order = env event.currency = 'JPY' event.save() def charge_create(**kwargs): assert kwargs['amount'] == 13 assert kwargs['currency'] == 'jpy' assert kwargs['source'] == 'tok_189fTT2eZvKYlo2CvJKzEzeu' c = MockedCharge() c.status = 'succeeded' c.paid = True return c monkeypatch.setattr("stripe.Charge.create", charge_create) prov = StripeCC(event) req = factory.post( '/', { 'stripe_token': 'tok_189fTT2eZvKYlo2CvJKzEzeu', 'stripe_last4': '4242', 'stripe_brand': 'Visa' }) req.session = {} prov.checkout_prepare(req, {}) assert 'payment_stripe_token' in req.session prov.payment_perform(req, order) order.refresh_from_db() assert order.status == Order.STATUS_PAID
def test_perform_failed(env, factory, monkeypatch): event, order = env def paymentintent_create(**kwargs): assert kwargs['amount'] == 1337 assert kwargs['currency'] == 'eur' assert kwargs['payment_method'] == 'pm_189fTT2eZvKYlo2CvJKzEzeu' c = MockedPaymentintent() c.status = 'failed' c.failure_message = 'Foo' c.charges.data[0].paid = True c.last_payment_error = Object() c.last_payment_error.message = "Foo" return c monkeypatch.setattr("stripe.PaymentIntent.create", paymentintent_create) prov = StripeCC(event) req = factory.post( '/', { 'stripe_payment_method_id': 'pm_189fTT2eZvKYlo2CvJKzEzeu', 'stripe_last4': '4242', 'stripe_brand': 'Visa' }) req.session = {} prov.checkout_prepare(req, {}) assert 'payment_stripe_payment_method_id' in req.session with pytest.raises(PaymentException): payment = order.payments.create(provider='stripe_cc', amount=order.total) prov.execute_payment(req, payment) order.refresh_from_db() assert order.status == Order.STATUS_PENDING
def charge_webhook(event, event_json, charge_id): prov = StripeCC(event) prov._init_api() try: charge = stripe.Charge.retrieve(charge_id) except stripe.error.StripeError: logger.exception('Stripe error on webhook. Event data: %s' % str(event_json)) return HttpResponse('Charge not found', status=500) metadata = charge['metadata'] if 'event' not in metadata: return HttpResponse('Event not given in charge metadata', status=200) if int(metadata['event']) != event.pk: return HttpResponse('Not interested in this event', status=200) try: order = event.orders.get(id=metadata['order'], payment_provider__startswith='stripe') except Order.DoesNotExist: return HttpResponse('Order not found', status=200) if order.payment_provider != prov.identifier: prov = event.get_payment_providers()[order.payment_provider] prov._init_api() order.log_action('pretix.plugins.stripe.event', data=event_json) is_refund = charge['refunds']['total_count'] or charge['dispute'] if order.status == Order.STATUS_PAID and is_refund: RequiredAction.objects.create( event=event, action_type='pretix.plugins.stripe.refund', data=json.dumps({ 'order': order.code, 'charge': charge_id })) elif order.status in ( Order.STATUS_PENDING, Order.STATUS_EXPIRED ) and charge['status'] == 'succeeded' and not is_refund: try: mark_order_paid(order, user=None) except LockTimeoutException: return HttpResponse("Lock timeout, please try again.", status=503) except Quota.QuotaExceededException: if not RequiredAction.objects.filter( event=event, action_type='pretix.plugins.stripe.overpaid', data__icontains=order.code).exists(): RequiredAction.objects.create( event=event, action_type='pretix.plugins.stripe.overpaid', data=json.dumps({ 'order': order.code, 'charge': charge.id })) return HttpResponse(status=200)
def test_refund_success(env, factory, monkeypatch): event, order = env def charge_retr(*args, **kwargs): def refund_create(): pass c = MockedCharge() c.refunds = MockedCharge() c.refunds.create = refund_create return c monkeypatch.setattr("stripe.Charge.retrieve", charge_retr) order.status = Order.STATUS_PAID order.payment_info = json.dumps({'id': 'ch_123345345'}) order.save() prov = StripeCC(event) req = factory.post('/', data={'auto_refund': 'auto'}) req.user = None prov.order_control_refund_perform(req, order) order.refresh_from_db() assert order.status == Order.STATUS_REFUNDED
def test_refund_unavailable(env, factory, monkeypatch): event, order = env def charge_retr(*args, **kwargs): def refund_create(): raise APIConnectionError(message='Foo') c = MockedCharge() c.refunds = object() c.refunds.create = refund_create() return c monkeypatch.setattr("stripe.Charge.retrieve", charge_retr) order.status = Order.STATUS_PAID order.payment_info = json.dumps({'id': 'ch_123345345'}) order.save() prov = StripeCC(event) req = factory.get('/') req.user = None prov.order_control_refund_perform(req, order) order.refresh_from_db() assert order.status == Order.STATUS_PAID
def stripe_verify_domain(event, domain): from pretix.plugins.stripe.payment import StripeCC prov = StripeCC(event) account = get_stripe_account_key(prov) if RegisteredApplePayDomain.objects.filter(account=account, domain=domain).exists(): return try: resp = stripe.ApplePayDomain.create(domain_name=domain, **prov.api_kwargs) except stripe.error.StripeError: logger.exception('Could not verify domain with Stripe') else: if resp.livemode: RegisteredApplePayDomain.objects.create(domain=domain, account=account)
def source_webhook(event, event_json, source_id): prov = StripeCC(event) prov._init_api() try: src = stripe.Source.retrieve(source_id, **prov.api_kwargs) except stripe.error.StripeError: logger.exception('Stripe error on webhook. Event data: %s' % str(event_json)) return HttpResponse('Charge not found', status=500) metadata = src['metadata'] if 'event' not in metadata: return HttpResponse('Event not given in charge metadata', status=200) if int(metadata['event']) != event.pk: return HttpResponse('Not interested in this event', status=200) with transaction.atomic(): try: order = event.orders.get(id=metadata['order'], payment_provider__startswith='stripe') except Order.DoesNotExist: return HttpResponse('Order not found', status=200) if order.payment_provider != prov.identifier: prov = event.get_payment_providers()[order.payment_provider] prov._init_api() order.log_action('pretix.plugins.stripe.event', data=event_json) go = (event_json['type'] == 'source.chargeable' and order.status == Order.STATUS_PENDING and src.status == 'chargeable') if go: try: prov._charge_source(None, source_id, order) except PaymentException: logger.exception('Webhook error') return HttpResponse(status=200)
def test_perform_card_error(env, factory, monkeypatch): event, order = env def charge_create(**kwargs): raise CardError(message='Foo', param='foo', code=100) monkeypatch.setattr("stripe.Charge.create", charge_create) prov = StripeCC(event) req = factory.post( '/', { 'stripe_token': 'tok_189fTT2eZvKYlo2CvJKzEzeu', 'stripe_last4': '4242', 'stripe_brand': 'Visa' }) req.session = {} prov.checkout_prepare(req, {}) assert 'payment_stripe_token' in req.session with pytest.raises(PaymentException): prov.payment_perform(req, order) order.refresh_from_db() assert order.status == Order.STATUS_PENDING
def test_perform_stripe_error(env, factory, monkeypatch): event, order = env def paymentintent_create(**kwargs): raise CardError(message='Foo', param='foo', code=100) monkeypatch.setattr("stripe.PaymentIntent.create", paymentintent_create) prov = StripeCC(event) req = factory.post( '/', { 'stripe_payment_method_id': 'pm_189fTT2eZvKYlo2CvJKzEzeu', 'stripe_last4': '4242', 'stripe_brand': 'Visa' }) req.session = {} prov.checkout_prepare(req, {}) assert 'payment_stripe_payment_method_id' in req.session with pytest.raises(PaymentException): payment = order.payments.create(provider='stripe_cc', amount=order.total) prov.execute_payment(req, payment) order.refresh_from_db() assert order.status == Order.STATUS_PENDING
def source_webhook(event, event_json, source_id, rso): prov = StripeCC(event) prov._init_api() try: src = stripe.Source.retrieve(source_id, **prov.api_kwargs) except stripe.error.StripeError: logger.exception('Stripe error on webhook. Event data: %s' % str(event_json)) return HttpResponse('Charge not found', status=500) metadata = src['metadata'] if 'event' not in metadata: return HttpResponse('Event not given in charge metadata', status=200) if int(metadata['event']) != event.pk: return HttpResponse('Not interested in this event', status=200) with transaction.atomic(): if rso and rso.payment: order = rso.payment.order payment = rso.payment elif rso: order = rso.order payment = None else: try: order = event.orders.get(id=metadata['order']) except Order.DoesNotExist: return HttpResponse('Order not found', status=200) payment = None if not payment: payment = order.payments.filter( info__icontains=src['id'], provider__startswith='stripe', amount=prov._amount_to_decimal(src['amount']) if src['amount'] is not None else order.total, ).last() if not payment: payment = order.payments.create( state=OrderPayment.PAYMENT_STATE_CREATED, provider=SOURCE_TYPES.get(src['type'], 'stripe'), amount=prov._amount_to_decimal(src['amount']) if src['amount'] is not None else order.total, info=str(src), ) if payment.provider != prov.identifier: prov = payment.payment_provider prov._init_api() order.log_action('pretix.plugins.stripe.event', data=event_json) go = (event_json['type'] == 'source.chargeable' and payment.state in (OrderPayment.PAYMENT_STATE_PENDING, OrderPayment.PAYMENT_STATE_CREATED) and src.status == 'chargeable') if go: try: prov._charge_source(None, source_id, payment) except PaymentException: logger.exception('Webhook error') elif src.status == 'failed': payment.info = str(src) payment.state = OrderPayment.PAYMENT_STATE_FAILED payment.order.log_action('pretix.event.order.payment.failed', { 'local_id': payment.local_id, 'provider': payment.provider, 'info': str(src) }) payment.save() return HttpResponse(status=200)
def charge_webhook(event, event_json, charge_id, rso): prov = StripeCC(event) prov._init_api() try: charge = stripe.Charge.retrieve(charge_id, expand=['dispute'], **prov.api_kwargs) except stripe.error.StripeError: logger.exception('Stripe error on webhook. Event data: %s' % str(event_json)) return HttpResponse('Charge not found', status=500) metadata = charge['metadata'] if 'event' not in metadata: return HttpResponse('Event not given in charge metadata', status=200) if int(metadata['event']) != event.pk: return HttpResponse('Not interested in this event', status=200) if rso and rso.payment: order = rso.payment.order payment = rso.payment elif rso: order = rso.order payment = None else: try: order = event.orders.get(id=metadata['order']) except Order.DoesNotExist: return HttpResponse('Order not found', status=200) payment = None if not payment: payment = order.payments.filter( info__icontains=charge['id'], provider__startswith='stripe', amount=prov._amount_to_decimal(charge['amount']), ).last() if not payment: payment = order.payments.create( state=OrderPayment.PAYMENT_STATE_CREATED, provider=SOURCE_TYPES.get(charge['source'].get('type', charge['source'].get('object', 'card')), 'stripe'), amount=prov._amount_to_decimal(charge['amount']), info=str(charge), ) if payment.provider != prov.identifier: prov = payment.payment_provider prov._init_api() order.log_action('pretix.plugins.stripe.event', data=event_json) is_refund = charge['refunds']['total_count'] or charge['dispute'] if is_refund: known_refunds = [r.info_data.get('id') for r in payment.refunds.all()] migrated_refund_amounts = [r.amount for r in payment.refunds.all() if not r.info_data.get('id')] for r in charge['refunds']['data']: a = prov._amount_to_decimal(r['amount']) if r['status'] in ('failed', 'canceled'): continue if a in migrated_refund_amounts: migrated_refund_amounts.remove(a) continue if r['id'] not in known_refunds: payment.create_external_refund( amount=a, info=str(r) ) if charge['dispute']: if charge['dispute']['status'] != 'won' and charge['dispute']['id'] not in known_refunds: a = prov._amount_to_decimal(charge['dispute']['amount']) if a in migrated_refund_amounts: migrated_refund_amounts.remove(a) else: payment.create_external_refund( amount=a, info=str(charge['dispute']) ) elif charge['status'] == 'succeeded' and payment.state in (OrderPayment.PAYMENT_STATE_PENDING, OrderPayment.PAYMENT_STATE_CREATED, OrderPayment.PAYMENT_STATE_CANCELED, OrderPayment.PAYMENT_STATE_FAILED): try: payment.confirm() except LockTimeoutException: return HttpResponse("Lock timeout, please try again.", status=503) except Quota.QuotaExceededException: pass elif charge['status'] == 'failed' and payment.state in (OrderPayment.PAYMENT_STATE_PENDING, OrderPayment.PAYMENT_STATE_CREATED): payment.info = str(charge) payment.state = OrderPayment.PAYMENT_STATE_FAILED payment.save() payment.order.log_action('pretix.event.order.payment.failed', { 'local_id': payment.local_id, 'provider': payment.provider, 'info': str(charge) }) return HttpResponse(status=200)
def source_webhook(event, event_json, source_id, rso): prov = StripeCC(event) prov._init_api() try: src = stripe.Source.retrieve(source_id, **prov.api_kwargs) except stripe.error.StripeError: logger.exception('Stripe error on webhook. Event data: %s' % str(event_json)) return HttpResponse('Charge not found', status=500) metadata = src['metadata'] if 'event' not in metadata: return HttpResponse('Event not given in charge metadata', status=200) if int(metadata['event']) != event.pk: return HttpResponse('Not interested in this event', status=200) with transaction.atomic(): if rso and rso.payment: order = rso.payment.order payment = rso.payment elif rso: order = rso.order payment = None else: try: order = event.orders.get(id=metadata['order']) except Order.DoesNotExist: return HttpResponse('Order not found', status=200) payment = None if not payment: payment = order.payments.filter( info__icontains=src['id'], provider__startswith='stripe', amount=prov._amount_to_decimal(src['amount']) if src['amount'] is not None else order.total, ).last() if not payment: payment = order.payments.create( state=OrderPayment.PAYMENT_STATE_CREATED, provider=SOURCE_TYPES.get(src['type'], 'stripe'), amount=prov._amount_to_decimal(src['amount']) if src['amount'] is not None else order.total, info=str(src), ) if payment.provider != prov.identifier: prov = payment.payment_provider prov._init_api() order.log_action('pretix.plugins.stripe.event', data=event_json) go = (event_json['type'] == 'source.chargeable' and payment.state in (OrderPayment.PAYMENT_STATE_PENDING, OrderPayment.PAYMENT_STATE_CREATED) and src.status == 'chargeable') if go: try: prov._charge_source(None, source_id, payment) except PaymentException: logger.exception('Webhook error') elif src.status == 'failed': payment.info = str(src) payment.state = OrderPayment.PAYMENT_STATE_FAILED payment.order.log_action('pretix.event.order.payment.failed', { 'local_id': payment.local_id, 'provider': payment.provider, 'info': str(src) }) payment.save() elif src.status == 'canceled' and payment.state in (Order.STATUS_PENDING, OrderPayment.PAYMENT_STATE_CREATED): payment.info = str(src) payment.state = OrderPayment.PAYMENT_STATE_CANCELED payment.save() return HttpResponse(status=200)