diff --git a/package-lock.json b/package-lock.json index 26ebac2..768ad2f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "@angular/router": "^22.0.0", "@angular/ssr": "^22.0.4", "@fortawesome/fontawesome-free": "^7.2.0", + "angularx-qrcode": "^21.0.5", "bootstrap": "^5.3.8", "express": "^5.1.0", "rxjs": "~7.8.0", @@ -3216,17 +3217,6 @@ "license": "MIT", "optional": true }, - "node_modules/@popperjs/core": { - "version": "2.11.8", - "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", - "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", - "license": "MIT", - "peer": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/popperjs" - } - }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.60.2", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.2.tgz", @@ -4101,6 +4091,20 @@ "node": ">= 14.0.0" } }, + "node_modules/angularx-qrcode": { + "version": "21.0.5", + "resolved": "https://registry.npmjs.org/angularx-qrcode/-/angularx-qrcode-21.0.5.tgz", + "integrity": "sha512-9Kv/Mi5tKzAkBL2xhYZyPaJEoq+b24mIpUAntp16kIBiozVnpGI6DbigTPJiSDkV7EpYHdjBJNGj8igFAHEp6g==", + "license": "MIT", + "dependencies": { + "qrcode": "1.5.4", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/common": "^21.0.0", + "@angular/core": "^21.0.0" + } + }, "node_modules/ansi-escapes": { "version": "7.3.0", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.3.0.tgz", @@ -4394,6 +4398,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/caniuse-lite": { "version": "1.0.30001799", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001799.tgz", @@ -4578,6 +4591,24 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, "node_modules/content-disposition": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", @@ -4759,6 +4790,15 @@ } } }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/decimal.js": { "version": "10.6.0", "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", @@ -4786,6 +4826,12 @@ "node": ">=8" } }, + "node_modules/dijkstrajs": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.3.tgz", + "integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==", + "license": "MIT" + }, "node_modules/dom-serializer": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", @@ -5237,6 +5283,19 @@ "url": "https://opencollective.com/express" } }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -5306,7 +5365,6 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, "license": "ISC", "engines": { "node": "6.* || 8.* || >= 10.*" @@ -5927,6 +5985,18 @@ "@lmdb/lmdb-win32-x64": "3.5.4" } }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/log-symbols": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-7.0.1.tgz", @@ -6703,6 +6773,33 @@ "license": "MIT", "optional": true }, + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/p-map": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.4.tgz", @@ -6716,6 +6813,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/pacote": { "version": "21.5.1", "resolved": "https://registry.npmjs.org/pacote/-/pacote-21.5.1.tgz", @@ -6824,6 +6930,15 @@ "node": ">= 0.8" } }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -6921,6 +7036,15 @@ "node": ">=16.20.0" } }, + "node_modules/pngjs": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz", + "integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==", + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/postcss": { "version": "8.5.15", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", @@ -7033,6 +7157,154 @@ "node": ">=6" } }, + "node_modules/qrcode": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.4.tgz", + "integrity": "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==", + "license": "MIT", + "dependencies": { + "dijkstrajs": "^1.0.1", + "pngjs": "^5.0.0", + "yargs": "^15.3.1" + }, + "bin": { + "qrcode": "bin/qrcode" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/qrcode/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/qrcode/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/qrcode/node_modules/cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "node_modules/qrcode/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/qrcode/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/qrcode/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/qrcode/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/qrcode/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/qrcode/node_modules/y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "license": "ISC" + }, + "node_modules/qrcode/node_modules/yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "license": "MIT", + "dependencies": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/qrcode/node_modules/yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "license": "ISC", + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/qs": { "version": "6.15.2", "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz", @@ -7093,6 +7365,15 @@ "dev": true, "license": "Apache-2.0" }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", @@ -7103,6 +7384,12 @@ "node": ">=0.10.0" } }, + "node_modules/require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "license": "ISC" + }, "node_modules/restore-cursor": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", @@ -7325,6 +7612,12 @@ "url": "https://opencollective.com/express" } }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "license": "ISC" + }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", @@ -8690,6 +8983,12 @@ "node": ">= 8" } }, + "node_modules/which-module": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", + "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", + "license": "ISC" + }, "node_modules/why-is-node-running": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", diff --git a/package.json b/package.json index 7b41eb7..5f1f94a 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "@angular/router": "^22.0.0", "@angular/ssr": "^22.0.4", "@fortawesome/fontawesome-free": "^7.2.0", + "angularx-qrcode": "^21.0.5", "bootstrap": "^5.3.8", "express": "^5.1.0", "rxjs": "~7.8.0", diff --git a/src/app/core/services/checkout.service.ts b/src/app/core/services/checkout.service.ts index 9290d71..f830484 100644 --- a/src/app/core/services/checkout.service.ts +++ b/src/app/core/services/checkout.service.ts @@ -11,13 +11,23 @@ import { BankAccount } from './tenant.interface'; export class CheckoutService { private readonly http = inject(HttpClient); - async getSelectedBankAccount(tenantCode: string): Promise { + async createPurchase(tenantCode: string, payload: any): Promise<{ id: number }> { const response = await firstValueFrom( - this.http.get<{ data: BankAccount }>(`${environment.url}tenants/${tenantCode}/bank-accounts/selected`) + this.http.post<{ data: { id: number } }>(`${environment.url}tenants/${tenantCode}/compras`, payload) ); if (!response?.data) { - throw new Error('No se encontraron los datos de la cuenta bancaria seleccionada.'); + throw new Error('Error al crear la compra.'); } return response.data; } + + async generatePaymentIntent(tenantCode: string, purchaseId: number, method: 'qr' | 'transfer' | 'telepagos'): Promise { + const response = await firstValueFrom( + this.http.post(`${environment.url}tenants/${tenantCode}/compras/${purchaseId}/payment-intent`, { method }) + ); + if (!response) { + throw new Error('Error al generar la intención de pago.'); + } + return response; + } } diff --git a/src/app/features/store/pages/checkout-page/checkout-page.component.html b/src/app/features/store/pages/checkout-page/checkout-page.component.html index 6a37b41..da2d08c 100644 --- a/src/app/features/store/pages/checkout-page/checkout-page.component.html +++ b/src/app/features/store/pages/checkout-page/checkout-page.component.html @@ -16,6 +16,8 @@ [selectedPaymentMethod]="selectedPaymentMethod()" [copiedTransferField]="copiedTransferField()" [transferAccount]="transferAccount()" + [isGeneratingIntent]="isGeneratingIntent()" + [qrData]="qrData()" (paymentMethodChange)="selectPaymentMethod($event)" (copyTransferValue)="copyTransferValue($event.field, $event.value)" (cancelStep)="onCancel()" diff --git a/src/app/features/store/pages/checkout-page/checkout-page.component.ts b/src/app/features/store/pages/checkout-page/checkout-page.component.ts index 25998a1..1afc433 100644 --- a/src/app/features/store/pages/checkout-page/checkout-page.component.ts +++ b/src/app/features/store/pages/checkout-page/checkout-page.component.ts @@ -63,7 +63,7 @@ export class CheckoutPageComponent { protected readonly isStep1Valid = signal(this.form.valid); protected readonly paymentMethods: ReadonlyArray = [ { id: 'qr', label: 'QR' }, - { id: 'transferencia', label: 'Transferencia' }, + { id: 'transfer', label: 'Transferencia' }, { id: 'telepagos', label: 'TelePagos' } ]; protected readonly selectedPaymentMethod = signal('qr'); @@ -75,29 +75,15 @@ export class CheckoutPageComponent { alias: 'telepagos.ar' }); + protected readonly isCreatingPurchase = signal(false); + protected readonly createdPurchaseId = signal(null); + protected readonly isGeneratingIntent = signal(false); + protected readonly qrData = signal(null); + constructor() { this.form.statusChanges .pipe(startWith(this.form.status)) .subscribe(() => this.isStep1Valid.set(this.form.valid)); - - void this.loadSelectedBankAccount(); - } - - private async loadSelectedBankAccount(): Promise { - const tenant = this.tenantService.tenant(); - if (!tenant) return; - - try { - const data = await this.checkoutService.getSelectedBankAccount(tenant.codigo); - this.transferAccount.set({ - titular: data.titular, - entidad: data.entidad, - cvu: data.cvu, - alias: data.alias - }); - } catch (error) { - console.error('Failed to load selected bank account:', error); - } } private mapCartItemToMock(item: CartItem): CartItemMock { @@ -130,16 +116,76 @@ export class CheckoutPageComponent { }; } - protected onStep1Continue(): void { - this.stepper.next(); + protected async onStep1Continue(): Promise { + if (this.form.invalid) return; + + const tenant = this.tenantService.tenant(); + if (!tenant) return; + + this.isCreatingPurchase.set(true); + + try { + const cartItems = this.cartService.cart()?.items || []; + const formValue = this.form.getRawValue(); + const payload = { + ...formValue, + nombre_apellido: formValue.nombre, + items: cartItems.map((item) => ({ + producto_variante_id: item.product_variant_id, + cantidad: item.cantidad + })) + }; + + const response = await this.checkoutService.createPurchase(tenant.codigo, payload); + this.createdPurchaseId.set(response.id); + + this.stepper.next(); + + // Auto trigger intent for default option + void this.selectPaymentMethod(this.selectedPaymentMethod()); + + } catch (error) { + console.error('Failed to create purchase:', error); + // Here we could show an alert or toast + } finally { + this.isCreatingPurchase.set(false); + } } protected onCancel(): void { void this.router.navigate(['/']); } - protected selectPaymentMethod(method: PaymentMethod): void { + protected async selectPaymentMethod(method: PaymentMethod): Promise { this.selectedPaymentMethod.set(method); + + const purchaseId = this.createdPurchaseId(); + const tenant = this.tenantService.tenant(); + + if (!purchaseId || !tenant || method === 'telepagos') { + return; + } + + this.isGeneratingIntent.set(true); + + try { + const response = await this.checkoutService.generatePaymentIntent(tenant.codigo, purchaseId, method); + + if (method === 'qr' && response.qr_data?.qr_code) { + this.qrData.set(response.qr_data.qr_code); + } else if (method === 'transfer' && response.transfer_data) { + this.transferAccount.set({ + titular: response.transfer_data.titular, + entidad: response.transfer_data.entidad, + cvu: response.transfer_data.cvu, + alias: response.transfer_data.alias + }); + } + } catch (error) { + console.error('Failed to generate payment intent:', error); + } finally { + this.isGeneratingIntent.set(false); + } } protected async copyTransferValue(field: TransferField, value: string): Promise { diff --git a/src/app/features/store/pages/checkout-page/checkout-page.models.ts b/src/app/features/store/pages/checkout-page/checkout-page.models.ts index ea06f64..238be11 100644 --- a/src/app/features/store/pages/checkout-page/checkout-page.models.ts +++ b/src/app/features/store/pages/checkout-page/checkout-page.models.ts @@ -1,6 +1,6 @@ import { FormControl, FormGroup } from '@angular/forms'; -export type PaymentMethod = 'qr' | 'transferencia' | 'telepagos'; +export type PaymentMethod = 'qr' | 'transfer' | 'telepagos'; export type TransferField = 'cvu' | 'alias'; export interface PaymentMethodOption { diff --git a/src/app/features/store/pages/checkout-page/checkout-payment-step.component.html b/src/app/features/store/pages/checkout-page/checkout-payment-step.component.html index e898e57..454f702 100644 --- a/src/app/features/store/pages/checkout-page/checkout-payment-step.component.html +++ b/src/app/features/store/pages/checkout-page/checkout-payment-step.component.html @@ -35,18 +35,26 @@
- @if (selectedPaymentMethod() === 'qr') { + @if (isGeneratingIntent()) { +
+

Cargando información de pago...

+
+ } @else if (selectedPaymentMethod() === 'qr') {

Ingresa a tu billetera y escanea el siguiente QR

- - - + @if (qrData()) { + + } @else { + + + + }
- } @else if (selectedPaymentMethod() === 'transferencia') { + } @else if (selectedPaymentMethod() === 'transfer') {

Transferi a la siguiente cuenta desde cualquier billetera

@@ -133,6 +141,7 @@ Finalizar compra diff --git a/src/app/features/store/pages/checkout-page/checkout-payment-step.component.scss b/src/app/features/store/pages/checkout-page/checkout-payment-step.component.scss index 39bca43..7cfb29e 100644 --- a/src/app/features/store/pages/checkout-page/checkout-payment-step.component.scss +++ b/src/app/features/store/pages/checkout-page/checkout-payment-step.component.scss @@ -219,6 +219,26 @@ ); background-size: 18px 18px; background-position: 0 0, 9px 9px; + display: flex; + justify-content: center; + align-items: center; + + qrcode { + display: flex; + justify-content: center; + align-items: center; + width: 100%; + height: 100%; + + ::ng-deep canvas, + ::ng-deep img { + display: block !important; + width: 100% !important; + height: 100% !important; + max-width: 100%; + max-height: 100%; + } + } } .qr-code__finder { diff --git a/src/app/features/store/pages/checkout-page/checkout-payment-step.component.ts b/src/app/features/store/pages/checkout-page/checkout-payment-step.component.ts index eb31710..3b28b98 100644 --- a/src/app/features/store/pages/checkout-page/checkout-payment-step.component.ts +++ b/src/app/features/store/pages/checkout-page/checkout-payment-step.component.ts @@ -1,5 +1,7 @@ import { ChangeDetectionStrategy, Component, input, output } from '@angular/core'; +import { QRCodeComponent } from 'angularx-qrcode'; + import { ButtonComponent } from '../../../../shared/components/button/button.component'; import { IconButtonComponent } from '../../../../shared/components/icon-button/icon-button.component'; import { PaymentMethod, PaymentMethodOption, TransferAccount, TransferField } from './checkout-page.models'; @@ -7,7 +9,7 @@ import { PaymentMethod, PaymentMethodOption, TransferAccount, TransferField } fr @Component({ selector: 'app-checkout-payment-step', standalone: true, - imports: [ButtonComponent, IconButtonComponent], + imports: [ButtonComponent, IconButtonComponent, QRCodeComponent], templateUrl: './checkout-payment-step.component.html', styleUrl: './checkout-payment-step.component.scss', changeDetection: ChangeDetectionStrategy.OnPush @@ -17,6 +19,8 @@ export class CheckoutPaymentStepComponent { readonly selectedPaymentMethod = input.required(); readonly copiedTransferField = input(null); readonly transferAccount = input.required(); + readonly isGeneratingIntent = input(false); + readonly qrData = input(null); readonly paymentMethodChange = output(); readonly copyTransferValue = output<{ field: TransferField; value: string }>();