Skip to content

Commit a8a79b7

Browse files
Ben Creechadrienverge
Ben Creech
authored andcommitted
Add event querying and payment_intent events
These features are technically separate but it's easier to test them together. The Stripe event query API lets us filter by event type and timestamp. Meanwhile payment_intent successes and failures generate events (this is particularly important for sepa_direct_debit which on the real Stripe API is typically pending for days, on the Stripe test API is typically pending for a minute or so, and in localstripe is pending for 0.5 seconds).
1 parent 6b0ff20 commit a8a79b7

File tree

2 files changed

+90
-2
lines changed

2 files changed

+90
-2
lines changed

localstripe/resources.py

+46-2
Original file line numberDiff line numberDiff line change
@@ -1115,6 +1115,47 @@ def _api_update(cls, id, **data):
11151115
def _api_delete(cls, id):
11161116
raise UserError(405, 'Method Not Allowed')
11171117

1118+
@classmethod
1119+
def _api_list_all(cls, url, type=None, created=None, limit=None,
1120+
starting_after=None, **kwargs):
1121+
if kwargs:
1122+
raise UserError(400, 'Unexpected ' + ', '.join(kwargs.keys()))
1123+
1124+
filters = []
1125+
try:
1126+
if type is not None:
1127+
assert _type(type) is str
1128+
filters.append(lambda obj: obj.type == type)
1129+
if created is not None:
1130+
assert _type(created) is dict
1131+
gt = try_convert_to_int(created.pop('gt', None))
1132+
if gt is not None:
1133+
filters.append(lambda obj: obj.created > gt)
1134+
1135+
gte = try_convert_to_int(created.pop('gte', None))
1136+
if gte is not None:
1137+
filters.append(lambda obj: obj.created >= gte)
1138+
1139+
lt = try_convert_to_int(created.pop('lt', None))
1140+
if lt is not None:
1141+
filters.append(lambda obj: obj.created < lt)
1142+
1143+
lte = try_convert_to_int(created.pop('lte', None))
1144+
if lte is not None:
1145+
filters.append(lambda obj: obj.created <= lte)
1146+
1147+
assert not created # no other params are supported
1148+
except AssertionError:
1149+
raise UserError(400, 'Bad request')
1150+
1151+
li = super()._api_list_all(
1152+
url, limit=limit, starting_after=starting_after
1153+
)
1154+
1155+
li._list = [obj for obj in li._list if all(f(obj) for f in filters)]
1156+
1157+
return li
1158+
11181159

11191160
class Invoice(StripeObject):
11201161
object = 'invoice'
@@ -1877,18 +1918,21 @@ def __init__(self, amount=None, currency=None, customer=None,
18771918
self._authentication_failed = False
18781919

18791920
def _on_success(self):
1921+
schedule_webhook(Event('payment_intent.succeeded', self))
18801922
if self.invoice:
18811923
invoice = Invoice._api_retrieve(self.invoice)
18821924
invoice._on_payment_success()
18831925

18841926
def _report_failure(self):
1927+
schedule_webhook(Event('payment_intent.payment_failed', self))
18851928
if self.invoice:
18861929
invoice = Invoice._api_retrieve(self.invoice)
18871930
invoice._on_payment_failure_now()
18881931

18891932
self.latest_charge._raise_failure()
18901933

1891-
def _on_failure_later(self):
1934+
def _report_async_failure(self):
1935+
schedule_webhook(Event('payment_intent.payment_failed', self))
18921936
if self.invoice:
18931937
invoice = Invoice._api_retrieve(self.invoice)
18941938
invoice._on_payment_failure_later()
@@ -1905,7 +1949,7 @@ def _create_charge(self, on_failure_now):
19051949
charge.payment_intent = self.id
19061950
self.latest_charge = charge
19071951
charge._initialize_charge(self._on_success, on_failure_now,
1908-
self._on_failure_later)
1952+
self._report_async_failure)
19091953

19101954
@property
19111955
def status(self):

test.sh

+44
Original file line numberDiff line numberDiff line change
@@ -1076,6 +1076,12 @@ payment_intent=$(
10761076
-d capture_method=manual \
10771077
| grep -oE 'pi_\w+' | head -n 1)
10781078

1079+
# we don't get a payment_intent.succeeded event from the pre-auth:
1080+
succeeded_event=$(
1081+
curl -sSfg -u $SK: "$HOST/v1/events?type=payment_intent.succeeded" \
1082+
| grep -oE "\"id\": \"$payment_intent\"" || true)
1083+
[ -z "$succeeded_event" ]
1084+
10791085
# payment_intent was not captured
10801086
captured=$(
10811087
curl -sSfg -u $SK: $HOST/v1/payment_intents/$payment_intent \
@@ -1096,6 +1102,12 @@ captured=$(
10961102
| grep -oE '"status": "succeeded"')
10971103
[ -n "$captured" ]
10981104

1105+
# we do get a payment_intent.succeeded event from the capture:
1106+
succeeded_event=$(
1107+
curl -sSfg -u $SK: "$HOST/v1/events?type=payment_intent.succeeded" \
1108+
| grep -oE "\"id\": \"$payment_intent\"")
1109+
[ -n "$succeeded_event" ]
1110+
10991111
# difference between pre-auth and capture is refunded
11001112
refunded=$(
11011113
curl -sSfg -u $SK: $HOST/v1/payment_intents/$payment_intent \
@@ -1173,6 +1185,38 @@ code=$(
11731185
-X POST -o /dev/null -w "%{http_code}")
11741186
[ "$code" = 402 ]
11751187

1188+
# we get a payment_intent.payment_failed event:
1189+
failed_event=$(
1190+
curl -sSfg -u $SK: "$HOST/v1/events?type=payment_intent.payment_failed" \
1191+
| grep -oE "\"id\": \"$payment_intent\"")
1192+
[ -n "$failed_event" ]
1193+
1194+
# we don't get a payment_intent.succeeded event:
1195+
succeeded_event=$(
1196+
curl -sSfg -u $SK: "$HOST/v1/events?type=payment_intent.succeeded" \
1197+
| grep -oE "\"id\": \"$payment_intent\"" || true)
1198+
[ -z "$succeeded_event" ]
1199+
1200+
## test event timestamp filtering:
1201+
first_created=$(
1202+
curl -sSfg -u $SK: "$HOST/v1/events" \
1203+
| grep -oP -m 1 'created": \K([0-9]+)' || true)
1204+
[ -n "$first_created" ]
1205+
1206+
total_count=$(curl -sSfg -u $SK: $HOST/v1/events \
1207+
| grep -oP '^ "total_count": \K([0-9]+)')
1208+
[ "$total_count" -gt 1 ]
1209+
1210+
count=$(
1211+
curl -sSfg -u $SK: "$HOST/v1/events?created[lte]=$first_created" \
1212+
| grep -oP '^ "total_count": \K([0-9]+)')
1213+
[ "$count" -le "$total_count" ]
1214+
1215+
count=$(
1216+
curl -sSfg -u $SK: "$HOST/v1/events?created[lte]=$first_created&created[gt]=9999999999" \
1217+
| grep -oP '^ "total_count": \K([0-9]+)')
1218+
[ "$count" -eq 0 ]
1219+
11761220
# Create a customer with card 4000000000000341 (that fails upon payment) and
11771221
# make sure creating the subscription doesn't fail (although it creates it with
11781222
# status 'incomplete'). This how Stripe behaves, see

0 commit comments

Comments
 (0)