ahivert / tgtg-python

Unofficial client for TooGoodToGo API
GNU General Public License v3.0
369 stars 70 forks source link

Howto do a order and automatic payment #215

Open NightFalcon650 opened 1 year ago

NightFalcon650 commented 1 year ago

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?

Jurrie commented 1 year ago

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.

solve-fx commented 1 year ago

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

Jurrie commented 1 year ago

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.

SamyMP commented 1 year ago

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.

Vivalemuc commented 1 year ago

@Jurrie

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

matteonu commented 1 year ago

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).

Vivalemuc commented 1 year ago

@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 ?

brandonbondig commented 1 year ago

@matteonu

Thanks for the solution, it worked perfectly!

Vivalemuc commented 1 year ago

@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..

brandonbondig commented 1 year ago

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)
Vivalemuc commented 1 year ago

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 commented 1 year ago

@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 :)

matteonu commented 1 year ago

@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.

Vivalemuc commented 1 year ago

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 !

Landwirt909 commented 12 months ago

@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']}")

Crankle437 commented 11 months ago

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

Nyantad commented 11 months ago

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!

SwannG commented 10 months ago

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'}

liulock commented 9 months ago

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

extern94 commented 8 months ago

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

hovak101 commented 7 months ago

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

extern94 commented 7 months ago

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

Vivalemuc commented 7 months ago

@extern94

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

hovak101 commented 7 months ago

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

sebastianpc commented 7 months ago

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

mehov commented 6 months ago

Hi everyone

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

The Adyen method doesn't work for me either.

floriegl commented 3 months ago

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 commented 3 months ago

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