I solved the problem. It's related to the atomic requests. I had "ATOMIC_REQUESTS": True in the database settings and XPAY's service responded so fast while the first atomic-request hadn't finished yet, so it prevented the other callback-url from accessing the same instance.
Thank you very much for your time and effort On Wednesday, August 21, 2019 at 4:32:03 PM UTC+2, mohammed habib wrote: > > What is the Transaction class which your model inherits from ? Is it just > a base model ? > > Let’s do an experiment > > Print(“object created”) right after the create() method > > Print(“callback requested”) on top of your callback method > > Which one do you see first ? > > Sent from my iPhone > > On 21 Aug 2019, at 16:56, Ahmed Shahwan <a...@shahwan.me <javascript:>> > wrote: > > That's the class, nothing different other than HTTP requests > > class CashPayment(BasePayment): > """Cash Payment Wrapper Class""" > > integration_id = CASH_INTEGRATION_ID > > def __init__(self, amount_cents: int, billing_data: dict): > super().__init__(amount_cents, billing_data) > > def pay_request(self): > """Step 4. issue Pay request""" > payload = { > "source": {"identifier": "cash", "subtype": "CASH"}, > "payment_token": self.payment_key_res.get("token"), > } > self.pay_req_res = self.hit_accept( > self._api_url + "acceptance/payments/pay", payload=payload > ) > > > On Wednesday, August 21, 2019 at 3:39:13 PM UTC+2, mohamed habib wrote: >> >> if you are able to share more code from the `utils.CashPayment` call that >> may help us >> >> On Wed, Aug 21, 2019 at 4:37 PM mohamed habib <moe....@gmail.com> wrote: >> >>> I highly doubt its related to ORM or postgres, the creation of the >>> record is synchronous so you can be sure that the record exists after the >>> create() call. >>> >>> Since you are sure that you are fetching the data properly we can rule >>> out this possibility >>> >>> Its probably a race condition of the callback arriving too soon *before* >>> you hit the create() call. So do you know at what point your payment >>> gateway will make the callback request and what would trigger it? Make sure >>> you are >>> >>> On Wed, Aug 21, 2019 at 4:19 PM Ahmed Shahwan <a...@shahwan.me> wrote: >>> >>>> I tried, same happened. it's related to how Postgres works internally >>>> with the Django-ORM. >>>> >>>> On Wednesday, August 21, 2019 at 3:07:47 PM UTC+2, mohamed habib wrote: >>>>> >>>>> Wild guess in the dark here, as I am unfamiliar with this API. you are >>>>> doing: >>>>> >>>>> `payment.pay_request()` >>>>> >>>>> before creating the WeacceptTransaction object. >>>>> >>>>> Maybe this line needs to move below: >>>>> `models.WeAcceptTransaction.objects.create(...)` ? >>>>> >>>>> At what point is the callback request triggered ? >>>>> >>>>> On Wed, Aug 21, 2019 at 3:36 PM Ahmed Shahwan <a...@shahwan.me> wrote: >>>>> >>>>>> Transaction model: >>>>>> >>>>>> >>>>>> class WeacceptTransaction(Transaction): >>>>>> """Weaccept's Transaction model""" >>>>>> >>>>>> # before frontend interaction >>>>>> auth_token = models.TextField() # got from step 1 >>>>>> order_id = models.IntegerField(verbose_name="Accept's Order ID") # >>>>>> id from step 2 >>>>>> order_url = models.CharField(max_length=255, null=True) # >>>>>> order_url from step 2 >>>>>> billing_data = JSONField() # provided in request for paymet key, >>>>>> step 3 >>>>>> payment_key = models.TextField() # payment_token from step 3 >>>>>> hmac = models.TextField(null=True) >>>>>> # after (from callback) >>>>>> success_status = models.BooleanField(default=False) >>>>>> is_refunded = models.BooleanField(default=False) >>>>>> reference_number = models.IntegerField(null=True) >>>>>> processed_clbk_json = JSONField(null=True) >>>>>> >>>>>> >>>>>> The cash_payment View: >>>>>> >>>>>> class CashPayment(APIView): >>>>>> """Cash Payment endpoint""" >>>>>> >>>>>> serializer_class = serializers.CardPaymentSerializer >>>>>> required_fields = ["amount_cents", "billing_data"] >>>>>> >>>>>> def post(self, request): >>>>>> # Validate required fields >>>>>> utils.validate(self.required_fields, request) >>>>>> payment = utils.CashPayment(**request.data) >>>>>> payment.prepare() >>>>>> payment.pay_request() >>>>>> # create a new transaction >>>>>> models.WeacceptTransaction.objects.create( >>>>>> merchant_order_id=payment.merchant_order_id, >>>>>> amount=payment.amount_cents, >>>>>> auth_token=payment.auth_res.get("token"), >>>>>> order_id=payment.order_reg_res.get("id"), >>>>>> order_url=payment.order_reg_res.get("order_url"), >>>>>> billing_data=payment.billing_data, >>>>>> payment_key=payment.payment_key_res.get("token"), >>>>>> hmac=payment.pay_req_res.get("hmac"), >>>>>> ) >>>>>> >>>>>> return Response( >>>>>> { >>>>>> "message": "Our representative will go to the address >>>>>> you provided " >>>>>> "to collect the cash from you", >>>>>> **payment.pay_req_res, >>>>>> } >>>>>> ) >>>>>> >>>>>> >>>>>> Please note: the first 3 steps are wrapped into the method `prepare()` >>>>>> the 4th step is `pay_request()` >>>>>> >>>>>> >>>>>> the callback-url endpoint (where the 5th step happens) >>>>>> >>>>>> class TransactionProcessedCallback(APIView): >>>>>> """Processed callback that will recieve "TRANSACTION", "TOKEN", " >>>>>> ORDER", >>>>>> "DELIVERY_STATUS" objects""" >>>>>> >>>>>> def post(self, request): >>>>>> # XXX extend to handle TOKEN, DELIVERY_STATUS objects... later >>>>>> t_obj = request.data.get("obj") # transaction object >>>>>> incoming_hmac = request.query_params.get("hmac") >>>>>> calculated_hmac = utils.calculate_hmac_transaction(t_obj) >>>>>> >>>>>> # if not equal hmac, not coming from Accept! >>>>>> if incoming_hmac != calculated_hmac: >>>>>> return Response( >>>>>> {"message": "invalid data"}, status=status. >>>>>> HTTP_400_BAD_REQUEST >>>>>> ) >>>>>> >>>>>> import ipdb >>>>>> >>>>>> ipdb.set_trace() >>>>>> # XXX: The error happens here >>>>>> transaction = models.WeacceptTransaction.objects.get( >>>>>> merchant_order_id=t_obj.get("order").get( >>>>>> "merchant_order_id") >>>>>> ) >>>>>> transaction.success_status = bool(t_obj.get("success")) >>>>>> transaction.is_refunded = bool(t_obj.get("is_refunded")) >>>>>> transaction.reference_number = int(t_obj.get("data").get( >>>>>> "transaction_no") or 1) >>>>>> transaction.processed_clbk_json = t_obj >>>>>> transaction.save( >>>>>> update_fields=[ >>>>>> "success_status", >>>>>> "is_refunded", >>>>>> "reference_number", >>>>>> "processed_clbk_json", >>>>>> ] >>>>>> ) >>>>>> >>>>>> email = t_obj.get("order").get("shipping_data").get("email") >>>>>> # TODO: send mail based upon success status >>>>>> try: >>>>>> send_mail( >>>>>> "Transaction Processed", >>>>>> "Your transaction is processed", >>>>>> "SERNDER MAIL", >>>>>> [email], >>>>>> fail_silently=False, >>>>>> ) >>>>>> except Exception as ex: >>>>>> print(">>>>>>>>>>FAILED TO SEND MAIL") >>>>>> print(ex) >>>>>> >>>>>> return Response({"message": "Transaction Updated!"}, status= >>>>>> status.HTTP_200_OK) >>>>>> >>>>>> >>>>>> The 6th step (redirect-url) is irrelevant here, and not included in >>>>>> the XPAY's service for this payment option. >>>>>> >>>>>> >>>>>> On Wednesday, August 21, 2019 at 2:03:20 PM UTC+2, mohammed habib >>>>>> wrote: >>>>>>> >>>>>>> Are you sure the callback request passes the right parameters ? >>>>>>> >>>>>>> Could you share some of your views code from the 6 steps, and your >>>>>>> Transaction model ? >>>>>>> >>>>>>> Sent from my iPhone >>>>>>> >>>>>>> On 21 Aug 2019, at 14:46, Ahmed Shahwan <a...@shahwan.me> wrote: >>>>>>> >>>>>>> Hi, >>>>>>> >>>>>>> I have a very strange problem that I don't know why it happens. >>>>>>> I'm creating a REST API service that wraps another REST API payment >>>>>>> service, let's call the other services XPAY. It has various payment >>>>>>> options >>>>>>> (Card, eWallet, Kiosk, and Cash). The steps required to use the >>>>>>> services >>>>>>> (to make a transaction) >>>>>>> >>>>>>> 1. authentication 2. order registration 3. payment key 4. prepare >>>>>>> frontend based on payment type 5. Callback URL 6. Redirect URL >>>>>>> >>>>>>> and each step depends on its predecessor. The first 4 steps are >>>>>>> performed through HTTP Requests that I issue from my service. I store >>>>>>> some >>>>>>> of the information I receive in those steps in a Model called >>>>>>> Transaction. >>>>>>> 5th and 6th steps are Callback endpoints that I create to receive >>>>>>> further >>>>>>> information about the status of the transaction that I started in the >>>>>>> first >>>>>>> 4 steps. >>>>>>> >>>>>>> My endpoints design is: >>>>>>> >>>>>>> myservice/payment/card >>>>>>> myservice/payment/wallet >>>>>>> myservice/payment/kiosk >>>>>>> myservice/payment/cash >>>>>>> >>>>>>> myservice/payment/callback-url >>>>>>> myservice/payment/redirect-url >>>>>>> >>>>>>> These endpoints are wrappers for the payment methods available in >>>>>>> XPAY's service that my frontend apps will use to provide those payment >>>>>>> options for users. >>>>>>> >>>>>>> I implemented the first 3 endpoints with no problem. inside those >>>>>>> endpoints, I create a transaction object with a unique id and then >>>>>>> update >>>>>>> it from inside the *callback-url endpoint* after I receive the >>>>>>> information from XPAY's service. Note that it takes some time between >>>>>>> creating the Transaction object and saving it into the database from >>>>>>> inside >>>>>>> my endpoint, and updating it from inside the callback url endpoint. >>>>>>> >>>>>>> The weird thing happens in the 4th endpoint `cash` endpoint. XPAY's >>>>>>> service responds very quickly on my *callback-url endpoint* and >>>>>>> when it gets to the updating step, I get *Transaction.DoesNotExist * >>>>>>> Exception while getting the transaction object >>>>>>> using Transaction.objects.get(id=id). >>>>>>> >>>>>>> After some debugging time using ipdb I found that the instance has >>>>>>> already been saved to the database! and I can retrieve it with its id. >>>>>>> but >>>>>>> when I let it run ordinarily the saving operation doesn't finish until >>>>>>> that >>>>>>> time (I got to know that using some print() statements), which is very >>>>>>> strange, it's impossible that my laptop is that slow! >>>>>>> >>>>>>> Solutions I tried: >>>>>>> 1. atomic transaction >>>>>>> 2. time.sleep some time after making the request and saving the >>>>>>> transaction object to the db >>>>>>> 3. time.sleep in the callback-url before getting the transaction >>>>>>> from DB >>>>>>> >>>>>>> I checked any possible logic error related to it and there's nothing >>>>>>> illogical. >>>>>>> >>>>>>> the issue is related to Postgres and how it operates, and also with >>>>>>> Django's ORM. I don't know. >>>>>>> >>>>>>> Main technologies I use: >>>>>>> Django 2.2 >>>>>>> Postgres Docker container >>>>>>> DRF >>>>>>> >>>>>>> I hope someone gives me an insight into why this is happening and >>>>>>> how to work around it. >>>>>>> >>>>>>> Thanks >>>>>>> >>>>>>> -- >>>>>>> You received this message because you are subscribed to the Google >>>>>>> Groups "Django users" group. >>>>>>> To unsubscribe from this group and stop receiving emails from it, >>>>>>> send an email to django...@googlegroups.com. >>>>>>> To view this discussion on the web visit >>>>>>> https://groups.google.com/d/msgid/django-users/e43be322-bab9-4162-9fbc-05c6c9b95c94%40googlegroups.com >>>>>>> >>>>>>> <https://groups.google.com/d/msgid/django-users/e43be322-bab9-4162-9fbc-05c6c9b95c94%40googlegroups.com?utm_medium=email&utm_source=footer> >>>>>>> . >>>>>>> >>>>>>> -- >>>>>> You received this message because you are subscribed to the Google >>>>>> Groups "Django users" group. >>>>>> To unsubscribe from this group and stop receiving emails from it, >>>>>> send an email to django...@googlegroups.com. >>>>>> To view this discussion on the web visit >>>>>> https://groups.google.com/d/msgid/django-users/a379e013-a01c-4c58-bf2b-0b4cd01f0fae%40googlegroups.com >>>>>> >>>>>> <https://groups.google.com/d/msgid/django-users/a379e013-a01c-4c58-bf2b-0b4cd01f0fae%40googlegroups.com?utm_medium=email&utm_source=footer> >>>>>> . >>>>>> >>>>> >>>>> >>>>> -- >>>>> Best regards, >>>>> *Mohammed M. Habib, PhD* >>>>> >>>>> -- >>>> You received this message because you are subscribed to the Google >>>> Groups "Django users" group. >>>> To unsubscribe from this group and stop receiving emails from it, send >>>> an email to django...@googlegroups.com. >>>> To view this discussion on the web visit >>>> https://groups.google.com/d/msgid/django-users/987c6ce7-70b6-49a9-ab3e-506e369d060e%40googlegroups.com >>>> >>>> <https://groups.google.com/d/msgid/django-users/987c6ce7-70b6-49a9-ab3e-506e369d060e%40googlegroups.com?utm_medium=email&utm_source=footer> >>>> . >>>> >>> >>> >>> -- >>> Best regards, >>> *Mohammed M. Habib, PhD* >>> >>> >> >> -- >> Best regards, >> *Mohammed M. Habib, PhD* >> >> -- > You received this message because you are subscribed to the Google Groups > "Django users" group. > To unsubscribe from this group and stop receiving emails from it, send an > email to django...@googlegroups.com <javascript:>. > To view this discussion on the web visit > https://groups.google.com/d/msgid/django-users/044d543f-5b6e-40d7-9ee6-36b7e84f5647%40googlegroups.com > > <https://groups.google.com/d/msgid/django-users/044d543f-5b6e-40d7-9ee6-36b7e84f5647%40googlegroups.com?utm_medium=email&utm_source=footer> > . > > -- You received this message because you are subscribed to the Google Groups "Django users" group. To unsubscribe from this group and stop receiving emails from it, send an email to django-users+unsubscr...@googlegroups.com. To view this discussion on the web visit https://groups.google.com/d/msgid/django-users/79ed22d5-675d-4362-834d-5f70f5e23856%40googlegroups.com.