Skip to content

Commit 5dfabd5

Browse files
committed
[#3793] Change payment reference number
The internal order ID isn't relevant anywore, instead we rely on the PK of the model. The race condition handling is also not relevant anymore as we rely on the PK which is already supposed to be unique.
1 parent 09ab59d commit 5dfabd5

File tree

2 files changed

+60
-41
lines changed

2 files changed

+60
-41
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Generated by Django 3.2.23 on 2024-01-30 13:36
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
("payments", "0007_alter_submissionpayment_plugin_options"),
10+
]
11+
12+
operations = [
13+
migrations.RemoveField(
14+
model_name="submissionpayment",
15+
name="order_id",
16+
),
17+
migrations.AlterField(
18+
model_name="submissionpayment",
19+
name="public_order_id",
20+
field=models.CharField(
21+
blank=True,
22+
help_text="The order ID to be sent to the payment provider. This ID is built by concatenating an optional global prefix, the submission public reference and a unique incrementing ID.",
23+
max_length=255,
24+
verbose_name="Order ID",
25+
),
26+
),
27+
]

src/openforms/payments/models.py

+33-41
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1+
from __future__ import annotations
2+
13
import uuid
24
from decimal import Decimal
35
from typing import TYPE_CHECKING
46

5-
from django.db import IntegrityError, models, transaction
6-
from django.db.models import Max, Sum
7+
from django.db import models, transaction
8+
from django.db.models import Sum
79
from django.utils.translation import gettext_lazy as _
810

911
from openforms.plugins.constants import UNIQUE_ID_MAX_LENGTH
@@ -14,18 +16,15 @@
1416
if TYPE_CHECKING:
1517
from openforms.submissions.models import Submission
1618

17-
ORDER_ID_START = 100
18-
ORDER_ID_PAD_LENGTH = 6
19-
2019

21-
class SubmissionPaymentManager(models.Manager):
20+
class SubmissionPaymentManager(models.Manager["SubmissionPayment"]):
2221
def create_for(
2322
self,
24-
submission: "Submission",
23+
submission: Submission,
2524
plugin_id: str,
2625
plugin_options: dict,
2726
amount: Decimal,
28-
):
27+
) -> SubmissionPayment:
2928
assert isinstance(amount, Decimal)
3029

3130
# first create without order_id
@@ -36,37 +35,29 @@ def create_for(
3635
amount=amount,
3736
)
3837
# then update with a unique order_id
39-
while True:
40-
try:
41-
with transaction.atomic():
42-
payment.order_id = self.get_next_order_id()
43-
payment.public_order_id = self.create_public_order_id_for(payment)
44-
payment.save(update_fields=("order_id", "public_order_id"))
45-
break
46-
except IntegrityError:
47-
# race condition on unique order_id
48-
continue
49-
return payment
5038

51-
def get_next_order_id(self) -> int:
52-
agg = self.aggregate(Max("order_id"))
53-
max_order_id = agg["order_id__max"] or ORDER_ID_START
54-
return max_order_id + 1
39+
with transaction.atomic():
40+
payment.public_order_id = self.create_public_order_id_for(payment)
41+
payment.save(update_fields=("public_order_id",))
42+
43+
return payment
5544

5645
@staticmethod
57-
def create_public_order_id_for(payment: "SubmissionPayment") -> str:
46+
def create_public_order_id_for(payment: SubmissionPayment) -> str:
5847
config = GlobalConfiguration.get_solo()
59-
prefix = config.payment_order_id_prefix
60-
prefix = prefix.replace("{year}", str(payment.created.year))
61-
order_id = str(payment.order_id).rjust(ORDER_ID_PAD_LENGTH, "0")
62-
return prefix + order_id
48+
prefix = config.payment_order_id_prefix.format(year=payment.created.year)
49+
if prefix:
50+
prefix = f"{prefix}_"
51+
return (
52+
f"{prefix}{payment.submission.public_registration_reference}_{payment.pk}"
53+
)
6354

6455

65-
class SubmissionPaymentQuerySet(models.QuerySet):
56+
class SubmissionPaymentQuerySet(models.QuerySet["SubmissionPayment"]):
6657
def sum_amount(self) -> Decimal:
6758
return self.aggregate(sum_amount=Sum("amount"))["sum_amount"] or Decimal("0")
6859

69-
def get_completed_public_order_ids(self) -> list[int]:
60+
def get_completed_public_order_ids(self) -> list[str]:
7061
return list(
7162
self.filter(
7263
status__in=(PaymentStatus.registered, PaymentStatus.completed)
@@ -88,17 +79,16 @@ class SubmissionPayment(models.Model):
8879
null=True,
8980
help_text=_("Copy of payment options at time of initializing payment."),
9081
)
91-
order_id = models.BigIntegerField(
92-
_("Order ID (internal)"),
93-
unique=True,
94-
null=True,
95-
help_text=_("Unique tracking across backend"),
96-
)
82+
# TODO Django 5.2 Update to a `GeneratedField`
9783
public_order_id = models.CharField(
9884
_("Order ID"),
99-
max_length=32,
85+
max_length=255,
10086
blank=True,
101-
help_text=_("Order ID stored with payment provider."),
87+
help_text=_(
88+
"The order ID to be sent to the payment provider. This ID is built by "
89+
"concatenating an optional global prefix, the submission public reference "
90+
"and a unique incrementing ID."
91+
),
10292
)
10393
amount = models.DecimalField(
10494
_("payment amount"),
@@ -114,7 +104,9 @@ class SubmissionPayment(models.Model):
114104
default=PaymentStatus.started,
115105
help_text=_("Status of the payment process in the configured backend."),
116106
)
117-
objects = SubmissionPaymentManager.from_queryset(SubmissionPaymentQuerySet)()
107+
objects: SubmissionPaymentManager = SubmissionPaymentManager.from_queryset(
108+
SubmissionPaymentQuerySet
109+
)()
118110

119111
class Meta:
120112
verbose_name = _("submission payment details")
@@ -127,8 +119,8 @@ class Meta:
127119
)
128120
]
129121

130-
def __str__(self):
131-
return f"#{self.order_id} '{self.get_status_display()}' {self.amount}"
122+
def __str__(self) -> str:
123+
return f"#{self.public_order_id} '{self.get_status_display()}' {self.amount}"
132124

133125
@property
134126
def form(self):

0 commit comments

Comments
 (0)