tgtg-python icon indicating copy to clipboard operation
tgtg-python copied to clipboard

Howto do a order and automatic payment

Open NightFalcon650 opened this issue 2 years ago • 28 comments

I love a Pythonscript to order tgtg boxes, but does anyone have a working pythonscript so you can order and automatically pay for exampe through iDeal?

NightFalcon650 avatar Feb 13 '23 15:02 NightFalcon650

For now, these functions are not implemented. You can figure them out yourself. Easiest way (though not all that easy) is to sniff the traffic of the TGTG app running on an Android emulator. You could use mitmproxy to sniff that traffic. You'll need an Android emulator (an AVD) with Google Play, and you'll need to insert the mitmproxy CA certificate as trusted system certificate. Not easy, and you're probably on your own, but it's definitely doable.

Jurrie avatar Feb 16 '23 08:02 Jurrie

Is this how you were able to find the endpoints and create your pull request @Jurrie?

solve-fx avatar Mar 20 '23 17:03 solve-fx

Yes it is. It's not all that hard actually:

  1. Install Android Studio.
  2. Create an Android Virtual Device (make sure to select an image with Google Play, otherwise the TGTG app will not run).
  3. Install the TGTG app (download it via something like APK Pure and use adb install <APK>).
  4. Root the AVD using the instructions here.
  5. Use mitmproxy or something similar to sniff the traffic (install the mitmproxy CA certificate to the system trust store first).

I wrote some documentation on how to do this for mitmproxy. My PR was merged, but no new release was done yet. So the documentation is not live. But it's on GitHub: check here. Using the latest release of mitmproxy (v9.0.1 currently) works just fine.

Jurrie avatar Mar 20 '23 19:03 Jurrie

Hi @Jurrie,

Thank you for the detailed tutorial. I am also interested in implementing such a method to automate payment. However, unfortunatly, I can not run Android Studio or even a simple emulator on my computer since I do not have enough memory, my computer becomes very laggy when I try to use virtualization.

Do you think it would be possible for you to share the code you implemented, even if it's not perfect and needs clean up (I can do that if you want) ? Of course, if there are any sensitive information (like card numbers hard coded), you can replace it with placeholders.

SamyMP avatar Apr 12 '23 16:04 SamyMP

@Jurrie

Can you share sample of the payment request bro ? Even if it's not perfect, just to see the work :)

Vivalemuc avatar May 30 '23 21:05 Vivalemuc

I made a fork that supports payments with credit cards (https://github.com/matteonu/tgtg-python). It should work for cards where no ThreeDS2 authorization is needed. For ThreeDS2 authorization support you need to be able to handle Adyen actions I think (https://docs.adyen.com/online-payments/3d-secure/native-3ds2/web-component?tab=create-new-component_2#3ds2-component).

matteonu avatar Jun 13 '23 08:06 matteonu

@matteonu Thanks for sharing :)

Is it possible to take the card on the account or not ? Because i made a bot for my friend, and i dont want to store the credit card information :) Or maybe using paypal accoun ?

Vivalemuc avatar Jun 13 '23 08:06 Vivalemuc

@matteonu

Thanks for the solution, it worked perfectly!

brandonbondig avatar Aug 09 '23 14:08 brandonbondig

@brandonbondig

Did you succeed to pay order with store card in TGTG pref ? I succeed to get the store card with v1/payments endpoint, i get the card identifier, but i dont succeed to pay order with this card. It will be so useful to avoid put the card each time etc..

Vivalemuc avatar Aug 13 '23 12:08 Vivalemuc

I haven't been successful in paying with the card stored on the TGTG app. By using @matteonu's method from the py-adyen-encrypt library on GitHub, I was able to successfully use my credit/debit card to pay for the reserved ID with TGTG's payment endpoint.

def pay_order(self, order_id, card_data: dict[str, str]):
        self.login()
        headers = {
            "User-Agent": USER_AGENTS[2],
            "Host": "checkoutshopper-live.adyen.com",
            "Connection": "Keep-Alive",
        }

        url = urljoin(BASE_URL_ADYEN, ADYEN_KEY_ENDPOINT)
        response = requests.request("GET", url, headers=headers, data={})

        ADYEN_KEY = response.json()["publicKey"]

        enc = encryptor(ADYEN_KEY)
        enc.adyen_version = "_0_1_1"
        card = enc.encrypt_card(
            card=card_data["card"],
            cvv=card_data["cvv"],
            month=card_data["month"],
            year=card_data["year"],
        )

        inner_payload = {
            "type": "scheme",
            "encryptedCardNumber": card["card"],
            "encryptedExpiryMonth": card["month"],
            "encryptedExpiryYear": card["year"],
            "encryptedSecurityCode": card["cvv"],
            "threeDS2SdkVersion": "2.2.10",
        }

        inner_payload_str = json.dumps(inner_payload).replace("/", "\/")
        payload = {
            "authorization": {
                "authorization_payload": {
                    "payload": inner_payload_str,
                    "payment_type": "CREDITCARD",
                    "save_payment_method": False,
                    "type": "adyenAuthorizationPayload",
                },
                "payment_provider": "ADYEN",
                "return_url": "adyencheckout://com.app.tgtg.itemview",
            }
        }

        payload_str = json.dumps(payload)

        response = self.session.post(
            url=self._get_url(PAY_ORDER_ENDPOINT.format(order_id)),
            headers=self._headers,
            data=payload_str,
            proxies=self.proxies,
            timeout=self.timeout,
        )

        if response.status_code == HTTPStatus.OK:
            return response.json()
        else:
            raise TgtgAPIError(response.status_code, response.content)

brandonbondig avatar Aug 13 '23 13:08 brandonbondig

yeah :( But i use it with many friends, and i dont want store the credit card on my server obviously :)

@matteonu I just need the payload when user click on "Pay" on the App with store card, did you have it ? Or maybe @Jurrie ?

Vivalemuc avatar Aug 13 '23 13:08 Vivalemuc

@Override // an.a public final Object invokeSuspend(Object obj) { Object obj2; c cVar; a aVar = a.COROUTINE_SUSPENDED; int i8 = this.f18862i; PaymentViewModel paymentViewModel = this.f18863j; PaymentMethods paymentMethods = this.f18864k; if (i8 == 0) { n0.F(obj); c cVar2 = paymentViewModel.f9077c; c3 c3Var = paymentViewModel.f9076b; String cardIdentifier = paymentMethods.getCardIdentifier(); this.f18861h = cVar2; this.f18862i = 1; e eVar = c3Var.f13481a; eVar.getClass(); g0 a5 = g0.a(1, "SELECT secret FROM biodata WHERE id = ?"); if (cardIdentifier == null) { a5.z(1); } else { a5.q(1, cardIdentifier); } obj2 = n.s(eVar.f15733a, new CancellationSignal(), new j4.e(eVar, 4, a5), this); if (obj2 == aVar) { return aVar; } cVar = cVar2; } else if (i8 == 1) { cVar = this.f18861h; try { n0.F(obj); obj2 = obj; } catch (Exception unused) { paymentViewModel.j(paymentMethods); return Unit.f19672a; } } else { throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine"); } String str = (String) obj2; KeyPair keyPair = this.f18865l; Intrinsics.c(keyPair); PrivateKey privateKey = keyPair.getPrivate(); Intrinsics.c(privateKey); cVar.getClass(); Intrinsics.checkNotNullParameter(str, "data"); Cipher cipher = cVar.f12264a; cipher.init(2, privateKey); byte[] doFinal = cipher.doFinal(Base64.decode(str, 0)); Intrinsics.checkNotNullExpressionValue(doFinal, "decodedData"); String str2 = new String(doFinal, b.f19720b); PaymentType paymentType = paymentMethods.getPaymentType(); Intrinsics.c(paymentType); PaymentViewModel.k(this.f18863j, new AuthorizationPayload(str2, (String) null, false, paymentType.name(), "adyenAuthorizationPayload", new AdyenCustomPayload(this.f18866m, paymentMethods.getCardIdentifier()).toJson(), (String) null, (String) null, (String) null, (String) null, 966, (DefaultConstructorMarker) null), this.f18864k, this.f18867n, this.f18868o, this.f18869p); return Unit.f19672a; }

I need to understand the str2 string, to pass at biometricssecret to use the store card :)

Vivalemuc avatar Aug 13 '23 13:08 Vivalemuc

@Vivalemuc Honestly, I think this is a lost cause. Also in the end you probably get your friends credit card info again but this time via TooGoodToGo. What you could try is to send your friends the private key, they encrypt their credit card info, send it to you and you then make the payment with the encrypted info. This could be automated of course.

matteonu avatar Aug 17 '23 16:08 matteonu

i cant do this in automatic process, because the user need to input the data in my program, so it's not secure..

If i get the Card ID store in TGTG app, and use it to pay order it will be totaly secure for me, and for users too !

Vivalemuc avatar Aug 18 '23 20:08 Vivalemuc

@brandonbondig I don't know why but for me the Payment always fails. Maybe I forgot something but this is what I have done:

order = client.create_order(item_id, 1) order_id = order['id'] time.sleep(1) payment = client.pay_order(order_id, card_data) payment_id = payment['payment_id'] time.sleep(1) print(f"Payment status: {client.get_payment_status(payment_id)['state']}")

Landwirt909 avatar Sep 07 '23 23:09 Landwirt909

@Landwirt909 I've the same issue. I've tried it with cards from Revolut (temp and virtual cards).

Crankle437 avatar Sep 15 '23 19:09 Crankle437

Hi I'm back on this thread but it was to know if there had been improvements on this automatic payment system or if someone else had done something else!

Nyantad avatar Oct 06 '23 20:10 Nyantad

Same issue on my side with 3 different cards.

On pay_order -> {'payment_id': '12345', 'order_id': '54321', 'payment_provider': 'ADYEN', 'state': 'AUTHORIZATION_INITIATED', 'user_id': '1111'}

On get_payment_status -> {'payment_id': '12345', 'order_id': '54321', 'payment_provider': 'ADYEN', 'state': 'FAILED', 'user_id': '1111'}

SwannG avatar Oct 15 '23 17:10 SwannG

tgtg updated the payment encryption, no wonder @matteonu 's method stop working.

liulock avatar Nov 27 '23 08:11 liulock

is there a way to manually pay for the created order?

extern94 avatar Dec 18 '23 19:12 extern94

I think this is a lost cause. The issue we have is figuring out the AES key, which we would need in order to encrypt the payload in order for Adyen to process our order. However, this is impossible to know without having access to the client code, which we do not. We only know the public RSA key, which is used for encrypting the AES key. But that doesn't help with actually encrypting and sending over the payload. Please let me know if I've misunderstood something, I'm a novice when it comes to encryption :) Shouldn't we close this issue now? @ahivert

hovak101 avatar Jan 17 '24 07:01 hovak101

i managed payment by using paypal and a telegram bot to send the payment link on my phone So if this semi automatic version is an option, it is easily implemented following the guide from @Jurrie

extern94 avatar Jan 17 '24 08:01 extern94

@extern94

Can you share your code snippet pls ? I only want paypal payment to buy order myself ! Thanks

Vivalemuc avatar Jan 17 '24 08:01 Vivalemuc

@extern94 got ya. That's a great solution! I'll have to look into it.

hovak101 avatar Jan 17 '24 08:01 hovak101

Hello @hovak101

It seems the AES key is randomly generated.

The following code should be the code behind it: https://gist.github.com/sebastianpc/16ab6a81f1273f8839dffb65e6ddad86

sebastianpc avatar Jan 27 '24 17:01 sebastianpc

Hi everyone

Can you please point to how can the payments be implemented using Paypal?

The Adyen method doesn't work for me either.

mehov avatar Feb 28 '24 12:02 mehov

Before create order (maybe even possible after the order is created): POST https://apptoogoodtogo.com/api/paymentMethod/v1/item/<item_id> with {"supported_types":[{"provider":"ADYEN","payment_types":["CREDITCARD","SOFORT","IDEAL","PAYPAL","BCMCMOBILE","BCMCCARD","VIPPS","TWINT","MBWAY","SWISH","BLIK","GOOGLEPAY"]},{"provider":"VOUCHER","payment_types":["VOUCHER","FAKE_DOOR"]},{"provider":"BRAINTREE","payment_types":["VENMO"]},{"provider":"CHARITY","payment_types":["CHARITY"]},{"provider":"SATISPAY","payment_types":["SATISPAY"]}]} and make sure that the response json has a payment_methods_state = "SUCCESS" and payment_methods[].payment_type = "PAYPAL"

Create order and make sure order is reserved with POST https://apptoogoodtogo.com/api/order/v7/<order.id>/status (response should have the JSON property "state" = "RESERVED"

POST https://apptoogoodtogo.com/api/order/v7/<orrder.id>/pay with {"authorization":{"authorization_payload":{"save_payment_method":false,"payment_type":"<payment_methods[]payment_type - should be 'PAYPAL'>","type":"adyenAuthorizationPayload","payload":<payment_methods[].adyen_api_payload>},"payment_provider":"<payment_methods[]-payment_provider - should be 'ADYEN'>","return_url":"adyencheckout://com.app.tgtg.itemview"}} (where payment_methods[].payment_type = "PAYPAL"; the payload is a JSON string with escape characters e.g., "{\"configuration\":{\"merchantId\":\"<retracted>\",\"intent\":\"authorize\"},\"name\":\"PayPal\",\"type\":\"paypal\"}"); save payment_id from the response json

POST https://apptoogoodtogo.com/api/payment/v3/<payment_id > - the response json has a payload property looking like this: "{\"method\":\"GET\",\"paymentMethodType\":\"paypal\",\"resendInterval\":0,\"resendMaxAttempts\":0,\"type\":\"redirect\",\"url\":\"https://live.adyen.com/hpp/checkout.shtml?u=redirectPayPal&p=<some_magic_payment_string>\"}", This live.adyen.com link should work for finishing the payment

floriegl avatar May 07 '24 23:05 floriegl

Also see: https://github.com/Der-Henning/tgtg/issues/171

floriegl avatar May 08 '24 00:05 floriegl