diff options
| author | Magnus Hagander | 2016-01-20 11:43:39 +0000 |
|---|---|---|
| committer | Magnus Hagander | 2016-01-20 18:56:38 +0000 |
| commit | ee5cd99ec2b9aed13c30592199d9661e17189dd3 (patch) | |
| tree | d3c97f5862f7f277cf7934ad4fda6687a92accaa /postgresqleu/paypal | |
| parent | d062ccfe9841a27ecab4b0e838cdee4acd93785f (diff) | |
Implement proper refund management
This means a few things:
1. We track refunds properly. Each invoice can be refunded once, but we now
also support partial refunds (which is important, as almost every refund we
make on a conference registration ends up being a partial one - if nothing
else it is deducted transaction fees).
2. We support API initiated refunds. That means that as long as the payment is
done using a supported provider (today that means Adyen and Paypal), the
complete invoice processing is done in the webapp, which means there is no
risk of getting the wrong numbers into the accounting (which has happened more
than once).
3. We now generate proper refund notices in PDF format for all refunds (both
automated and manual). The user receives this one as well as status updates
throughout the refund process.
Actual API refunds are handled by a new cronjob, to ensure that we don't end up
blocking the user if the API is slow. Initiating the refund puts it on the queue,
the cronjob sends it to the provider, and the notification from the provider
flags it as completed (and generates the refund notice).
Diffstat (limited to 'postgresqleu/paypal')
| -rwxr-xr-x | postgresqleu/paypal/management/commands/paypal_match.py | 19 | ||||
| -rw-r--r-- | postgresqleu/paypal/util.py | 17 |
2 files changed, 34 insertions, 2 deletions
diff --git a/postgresqleu/paypal/management/commands/paypal_match.py b/postgresqleu/paypal/management/commands/paypal_match.py index 9e414891..52aa11c3 100755 --- a/postgresqleu/paypal/management/commands/paypal_match.py +++ b/postgresqleu/paypal/management/commands/paypal_match.py @@ -62,9 +62,24 @@ class Command(BaseCommand): ] create_accounting_entry(trans.timestamp.date(), accrows, True, urls) continue - # Record type: payment (or refund) + if trans.amount < 0 and trans.transtext.startswith('Refund of Paypal payment: PGEU refund '): + trans.setmatched('Matched API initiated refund') + # API initiated refund, so we should be able to match it + invoicemanager.complete_refund( + trans.transtext[38:], # 38 is the length of the string above + -trans.amount, + -trans.fee, + settings.ACCOUNTING_PAYPAL_INCOME_ACCOUNT, + settings.ACCOUNTING_PAYPAL_FEE_ACCOUNT, + urls, + InvoicePaymentMethod.objects.get(classname='postgresqleu.util.payment.paypal.Paypal'), + ) + + # Accounting record is created by invoice manager + continue + # Record type: outgoing payment (or manual refund) if trans.amount < 0: - trans.setmatched('Payment or refund, automatically matched by script') + trans.setmatched('Outgoing payment or manual refund, automatically matched by script') # Refunds typically have a fee (a reversed fee), whereas pure # payments don't have one. We don't make a difference of them # though - we leave the record open for manual verification diff --git a/postgresqleu/paypal/util.py b/postgresqleu/paypal/util.py index d9c2e4c3..03934a14 100644 --- a/postgresqleu/paypal/util.py +++ b/postgresqleu/paypal/util.py @@ -49,3 +49,20 @@ class PaypalAPI(object): return self._api_call('GetTransactionDetails', { 'TRANSACTIONID': transactionid, }) + + + def refund_transaction(self, paypaltransid, amount, isfull, refundnote): + r = self._api_call('RefundTransaction', { + 'TRANSACTIONID': paypaltransid, + 'REFUNDTYPE': isfull and 'Full' or 'Partial', + 'AMT': '{0:.2f}'.format(amount), + 'CURRENCYCODE': settings.CURRENCY_ISO, + 'NOTE': refundnote, + }) + + # We ignore the status here as we will parse it from the + # actual statement later. + if r['ACK'][0] == 'Success': + return r['REFUNDTRANSACTIONID'][0] + + raise Exception(r) |
