Debugging calls to other services: design for the failure
Every outbound call is a promise about a machine you do not control. Bugs in this area rarely appear when the other service is healthy, so the skill this section trains is making the other side misbehave on purpose and watching what your code does.
Reproduce by making the dependency slow, flaky or different
The lab environment ships a stand-in for the payments provider that you can make slow, make fail after it has done the work, or make answer in a new format. Use that: put the dependency into the bad state, run the operation, and watch three things. How long does the request wait? What state does your database end up in? What does the customer see? A ticket that says "checkout hangs" is answered by the first; "customers were charged twice" by the second.
Every call needs a timeout and a failure mode
The requests library waits forever unless you give it a timeout. While a call waits, a worker thread and usually a database transaction are held, so one slow dependency can starve unrelated pages. Decide, for each outbound call, the longest you are willing to wait (separate connect and read limits), what happens when that expires (retry? which error to the user?), and whether retrying is safe. Retrying a charge is only safe if the provider can recognise it as the same request, which is what an idempotency key is for.
Fail loudly and locally
When a dependency changes or breaks, the worst outcome is a silent default: a missing field treated as zero, an unexpected status treated as declined. Prefer strict parsing that raises a clear error at the boundary, and translate provider failures into your own explicit error types and HTTP answers (service unavailable, bad gateway). Then test the boundary: a fake client that times out, fails, or answers in the wrong shape.