Paysafe PayHub Analysis
===
Positive scenario
1. Call Paysafe to get or create payment handle
2. Call Paysafe to get or create standalone credit
3. Update withdraw record to complete / refund
Error cases
1.1 get payment handle times out / internal error -> retry until it returns
1.2 get payment handle not payable / expired -> create new payment handle
1.3 get payment handle okay -> step 2
1.4 create new payment handle -> timeout / internal error -> retry
1.5 create new payment handle returns validation error -> step 3
1.6 create new payment handle returns duplicate -> new ref ID
1.7 create payment handle returns payable -> go to step 2
2.1 get standalone credit times out / internal error -> retry until it returns
2.2 get standalone credit returns payment handle expired -> create new payment handle
2.3 get standalone credit returns payment handle paid / invalid / not payable -> step 3
2.4 get standalone credit returns payable -> create standalone credit
2.5 create standalone credit times out / internal error (depend on exception message)-> retry with same merchant ref
2.6 retry create standalone credit returns duplicate -> step 1
3.1 update record fails -> retry
3.2 send to servicebus fails -> retry
Structure
```
getStandaloneCredit(merchantRef): internally retry for exceptions or pending.
returns Complete | NotExist | NotPayable | Expired
getOrCreatePaymentHandle: internally polly retry for exception or pending.
return value: OK | Rejected
createPaymentHandle(merchantRef): internally polly retry for exception.
return value: OK | Rejected | Duplicate | TokenExpired
```
Guiderail: if instant retry (or backoff is <60s), use polly. otherwise use ADF.
Activity:
```
// merchantRef is generated and saved to withdraw record outside this activity
var merchantRef = getMerchantRef(); // FromWithdrawRecord
var ret = getStandaloneCredit(merchantRef);
if (ret == NotExist)
{
ret = getOrCreatePaymentHandle(merchantRef);
if (ret == OK)
ret = Payable
else
ret = NotPayable
}
if (ret == Payable)
{
ret = createStandaloneCredit(ret.PaymentToken, merchantRef);
if (ret == OK)
ret = Complete
else if (ret == Rejected)
ret = NotPayable
else if (ret == Duplicate)
{
// The only situation duplicate could occur is createStandaloneCredit times out then get retried.
ret = getStandaloneCredit(merchantRef);
// we will get status for the standalone credit one more time. Assuming createStandaloneCredit will return complete or not payable straight away, it is pretty rare that payment token is still in Payable / Expired state. If it happens we treat it as a failure and refund.
if (ret != Complete)
ret = NotPayable;
}
// token expiry will fall through
}
if (ret == Expired)
{
merchantRef = new Guid();
saveMerchantRef(merchantRef); // to withdraw record
throw Exception; // for retry
}
// ret == NotPayable || ret == Complete
return ret;
```
```
getStandaloneCredit()
{
using var resp = httpClient.Get();
if(resp.data.status == Pending)
throw Exception
}
```
Reentrrant / idempotency analysis: PASSED