Add online business credit card application flow#83
Draft
Copilot wants to merge 5 commits into
Draft
Conversation
…tion Agent-Logs-Url: https://github.com/yortch/agentic-devops-demo/sessions/bce36393-2a37-41ad-9d16-32e70e8f035c Co-authored-by: yortch <4576246+yortch@users.noreply.github.com>
Agent-Logs-Url: https://github.com/yortch/agentic-devops-demo/sessions/bce36393-2a37-41ad-9d16-32e70e8f035c Co-authored-by: yortch <4576246+yortch@users.noreply.github.com>
…n, filter bug, UI fixes Agent-Logs-Url: https://github.com/yortch/agentic-devops-demo/sessions/bce36393-2a37-41ad-9d16-32e70e8f035c Co-authored-by: yortch <4576246+yortch@users.noreply.github.com>
…elds display, add URL hint text Agent-Logs-Url: https://github.com/yortch/agentic-devops-demo/sessions/bce36393-2a37-41ad-9d16-32e70e8f035c Co-authored-by: yortch <4576246+yortch@users.noreply.github.com>
Copilot
AI
changed the title
[WIP] Add secure online application for Three Rivers Bank credit card
Add online business credit card application flow
Apr 21, 2026
Contributor
There was a problem hiding this comment.
Pull request overview
Adds an end-to-end online business credit card application flow to the Three Rivers Bank demo: new “Apply Now” entry points in the UI, a multi-step application wizard + confirmation screen, and a backend POST /api/applications endpoint that persists applications while encrypting sensitive fields.
Changes:
- Frontend: Adds
/apply/:cardIdwizard and/apply/:cardId/confirmation, plus “Apply Now” buttons from card detail/comparison pages and a submit API helper. - Backend: Introduces
BusinessCreditCardApplicationpersistence model, repository, service logic (validation + rate limiting + encryption), and controller endpoint. - Config/deps: Adds encryption key config and frontend form/validation dependencies (react-hook-form/yup).
Show a summary per file
| File | Description |
|---|---|
| frontend/src/services/api.js | Adds applicationService.submitApplication() for posting application payloads. |
| frontend/src/pages/CardDetailsPage.jsx | Wires “Apply Now” CTA to navigate to the application route. |
| frontend/src/pages/CardComparisonPage.jsx | Adds “Apply Now” buttons in both grid and table views. |
| frontend/src/pages/ApplicationFormPage.jsx | New 5-step application wizard with validation and draft auto-save. |
| frontend/src/pages/ApplicationConfirmationPage.jsx | New confirmation page displaying reference/status/timeline and print action. |
| frontend/src/App.jsx | Registers new /apply/:cardId and confirmation routes. |
| frontend/package.json | Adds react-hook-form, yup, and @hookform/resolvers dependencies. |
| frontend/package-lock.json | Locks new dependency tree for the application form stack. |
| backend/src/main/resources/application.yml | Adds app.encryption.key configuration (env var with default). |
| backend/src/main/java/com/threeriversbank/service/ApplicationService.java | Implements submit + validation + encryption + persistence. |
| backend/src/main/java/com/threeriversbank/repository/ApplicationRepository.java | Adds JPA repository + rate-limit count query. |
| backend/src/main/java/com/threeriversbank/model/entity/BusinessCreditCardApplication.java | New JPA entity to store applications (with encrypted Tax ID/SSN). |
| backend/src/main/java/com/threeriversbank/model/dto/ApplicationRequestDto.java | New request DTO with Bean Validation constraints. |
| backend/src/main/java/com/threeriversbank/model/dto/ApplicationResponseDto.java | New response DTO returned to the frontend confirmation page. |
| backend/src/main/java/com/threeriversbank/controller/ApplicationController.java | New POST /api/applications endpoint + client IP extraction. |
Copilot's findings
Files not reviewed (1)
- frontend/package-lock.json: Language not supported
- Files reviewed: 14/15 changed files
- Comments generated: 12
Comment on lines
+17
to
+18
| @Query("SELECT COUNT(a) FROM BusinessCreditCardApplication a WHERE a.ipAddress = :ipAddress AND a.submittedAt >= :since") | ||
| long countByIpAddressSince(String ipAddress, LocalDateTime since); |
| # Application encryption configuration | ||
| app: | ||
| encryption: | ||
| key: ${APPLICATION_ENCRYPTION_KEY:ThreeRiversBank!} |
Comment on lines
+138
to
+155
| private String encrypt(String data) { | ||
| try { | ||
| byte[] keyBytes = encryptionKey.getBytes(StandardCharsets.UTF_8); | ||
| // Ensure exactly 16 bytes for AES-128 | ||
| byte[] paddedKey = new byte[16]; | ||
| System.arraycopy(keyBytes, 0, paddedKey, 0, Math.min(keyBytes.length, 16)); | ||
| SecretKeySpec keySpec = new SecretKeySpec(paddedKey, "AES"); | ||
| Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); | ||
| byte[] iv = new byte[16]; | ||
| new SecureRandom().nextBytes(iv); | ||
| IvParameterSpec ivSpec = new IvParameterSpec(iv); | ||
| cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec); | ||
| byte[] encrypted = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8)); | ||
| // Prepend IV to ciphertext for storage | ||
| byte[] ivAndCiphertext = new byte[16 + encrypted.length]; | ||
| System.arraycopy(iv, 0, ivAndCiphertext, 0, 16); | ||
| System.arraycopy(encrypted, 0, ivAndCiphertext, 16, encrypted.length); | ||
| return Base64.getEncoder().encodeToString(ivAndCiphertext); |
Comment on lines
+44
to
+50
| // Rate limiting check | ||
| LocalDateTime oneDayAgo = LocalDateTime.now().minusDays(1); | ||
| long applicationsToday = applicationRepository.countByIpAddressSince(ipAddress, oneDayAgo); | ||
| if (applicationsToday >= MAX_APPLICATIONS_PER_DAY) { | ||
| log.warn("Rate limit exceeded for IP: {}", ipAddress); | ||
| throw new IllegalStateException("Maximum of " + MAX_APPLICATIONS_PER_DAY + " applications per day exceeded. Please try again tomorrow."); | ||
| } |
Comment on lines
+49
to
+52
| String xForwardedFor = request.getHeader("X-Forwarded-For"); | ||
| if (xForwardedFor != null && !xForwardedFor.isEmpty()) { | ||
| return xForwardedFor.split(",")[0].trim(); | ||
| } |
Comment on lines
+583
to
+599
| <Typography variant="subtitle1" fontWeight={700}>Business Information</Typography> | ||
| <Button size="small" onClick={() => setActiveStep(0)}>Edit</Button> | ||
| </Box> | ||
| <Grid container spacing={1}> | ||
| <Grid size={{ xs: 6 }}><Typography variant="body2" color="text.secondary">Legal Name</Typography></Grid> | ||
| <Grid size={{ xs: 6 }}><Typography variant="body2">{formValues.businessLegalName}</Typography></Grid> | ||
| {formValues.dbaName && (<><Grid size={{ xs: 6 }}><Typography variant="body2" color="text.secondary">DBA</Typography></Grid><Grid size={{ xs: 6 }}><Typography variant="body2">{formValues.dbaName}</Typography></Grid></>)} | ||
| <Grid size={{ xs: 6 }}><Typography variant="body2" color="text.secondary">Structure</Typography></Grid> | ||
| <Grid size={{ xs: 6 }}><Typography variant="body2">{formValues.businessStructure}</Typography></Grid> | ||
| <Grid size={{ xs: 6 }}><Typography variant="body2" color="text.secondary">Tax ID</Typography></Grid> | ||
| <Grid size={{ xs: 6 }}><Typography variant="body2">{formValues.taxId ? `***-**-${formValues.taxId.slice(-4)}` : 'Not entered'}</Typography></Grid> | ||
| <Grid size={{ xs: 6 }}><Typography variant="body2" color="text.secondary">Industry</Typography></Grid> | ||
| <Grid size={{ xs: 6 }}><Typography variant="body2">{formValues.industry}</Typography></Grid> | ||
| <Grid size={{ xs: 6 }}><Typography variant="body2" color="text.secondary">Annual Revenue</Typography></Grid> | ||
| <Grid size={{ xs: 6 }}><Typography variant="body2">{formValues.annualRevenue}</Typography></Grid> | ||
| <Grid size={{ xs: 6 }}><Typography variant="body2" color="text.secondary">Address</Typography></Grid> | ||
| <Grid size={{ xs: 6 }}><Typography variant="body2">{formValues.businessStreet}, {formValues.businessCity}, {formValues.businessState} {formValues.businessZip}</Typography></Grid> |
Comment on lines
+118
to
+119
| A confirmation email will be sent to <strong>{confirmation.ownerEmail}</strong>. | ||
| Our team will review your application and contact you with a decision. |
Comment on lines
+201
to
+202
| const stepSchemas = [businessSchema, personalSchema, preferencesSchema, termsSchema]; | ||
|
|
Comment on lines
+27
to
+46
| @PostMapping | ||
| @Operation(summary = "Submit credit card application", description = "Submits a new business credit card application") | ||
| public ResponseEntity<?> submitApplication( | ||
| @Valid @RequestBody ApplicationRequestDto request, | ||
| HttpServletRequest httpRequest) { | ||
| log.info("POST /api/applications - card ID: {}", request.getCreditCardId()); | ||
| String ipAddress = getClientIp(httpRequest); | ||
| try { | ||
| ApplicationResponseDto response = applicationService.submitApplication(request, ipAddress); | ||
| return ResponseEntity.status(HttpStatus.CREATED).body(response); | ||
| } catch (IllegalStateException e) { | ||
| log.warn("Rate limit or business rule violation: {}", e.getMessage()); | ||
| return ResponseEntity.status(HttpStatus.TOO_MANY_REQUESTS) | ||
| .body(Map.of("error", e.getMessage())); | ||
| } catch (IllegalArgumentException e) { | ||
| log.warn("Validation error: {}", e.getMessage()); | ||
| return ResponseEntity.status(HttpStatus.BAD_REQUEST) | ||
| .body(Map.of("error", e.getMessage())); | ||
| } | ||
| } |
Comment on lines
+203
to
+221
| const ApplicationFormPage = () => { | ||
| const { cardId } = useParams(); | ||
| const navigate = useNavigate(); | ||
| const [activeStep, setActiveStep] = useState(0); | ||
| const [completedSteps, setCompletedSteps] = useState(new Set()); | ||
| const autoSaveRef = useRef(null); | ||
|
|
||
| const { data: card, isLoading: cardLoading } = useQuery({ | ||
| queryKey: ['creditCard', cardId], | ||
| queryFn: () => creditCardService.getCardById(cardId), | ||
| }); | ||
|
|
||
| const submitMutation = useMutation({ | ||
| mutationFn: (data) => applicationService.submitApplication({ ...data, creditCardId: Number(cardId) }), | ||
| onSuccess: (data) => { | ||
| localStorage.removeItem(STORAGE_KEY + '_' + cardId); | ||
| navigate(`/apply/${cardId}/confirmation`, { state: { confirmation: data } }); | ||
| }, | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Enables small business owners to apply for a Three Rivers Bank credit card entirely online. Adds a multi-step application form, backend persistence with encrypted sensitive fields, and "Apply Now" entry points on card listing and detail pages.
Backend
BusinessCreditCardApplication— stores all application fields; SSN and Tax ID columns sized for AES-encrypted Base64 outputPOST /api/applications— validates input (Spring@Valid), enforces rate limit (3/day per IP), validates applicant age (18+), encrypts SSN/Tax ID with AES-128/CBC + random IV before persistence, returns application ID and decision timeline${APPLICATION_ENCRYPTION_KEY}(falls back to dev default); key bytes normalized to 16 bytes with UTF-8 charsetFrontend
/apply/:cardId—ApplicationFormPage: 5-step wizard (Business Info → Personal Info → Card Preferences → Terms & Conditions → Review)localStorageevery 30s; SSN, Tax ID, and DOB are explicitly excluded from persistence***-**-1234); "Edit" links jump back to specific steps/apply/:cardId/confirmation—ApplicationConfirmationPage: shows reference number, decision timeline ("5–7 business days"), print summary buttonCardDetailsPageandCardComparisonPage(both grid and table views)applicationService.submitApplication()added toapi.jsSecurity notes
localStorageX-Forwarded-Forused for IP rate limiting — assumes trusted reverse proxy in deploymentOriginal prompt
Apply for credit card
Work Item Details
Note: Please focus on the descriptions and information that provide context about the task requirements, functionality, and implementation details. Dates, priorities, and administrative metadata are less relevant for coding tasks.
Description
As a small business owner
I want to complete a secure online application for a Three Rivers Bank business credit card
So that I can access financing and rewards to support my business expenses without visiting a branch
Acceptance Criteria
"Apply Now" button is prominently displayed on card detail pages
"Apply Now" button is available on card comparison page for each card
Clicking "Apply Now" navigates to a dedicated application form page (/apply/{cardId})
Selected card information is pre-populated and displayed at top of application form
Business legal name (required, text field)
Doing Business As (DBA) name (optional, text field)
Business structure dropdown (required: Sole Proprietorship, LLC, Corporation, Partnership, Non-Profit)
Tax ID / EIN (required, 9-digit format validation)
Industry/Business type dropdown (required)
Years in business (required, numeric)
Number of employees (required, numeric)
Annual business revenue (required, currency field with dropdown ranges)
Business address fields (street, city, state, ZIP - all required)
Business phone number (required, phone format validation)
Business website (optional, URL validation)
First and last name (required)
Date of birth (required, 18+ validation)
SSN (required, 9-digit format, encrypted on submission)
Email address (required, email format validation)
Home address (required - street, city, state, ZIP)
Mobile phone (required, phone format validation)
Percentage ownership (required, numeric 0-100)
Title/Position (required, text field)
Annual personal income (required, currency field)
Requested credit limit dropdown (required: $5k, $10k, $25k, $50k, $100k+)
Number of employee cards needed (optional, numeric 0-50)
Authorized user information (expandable section, optional)
Real-time field validation with error messages
Required field indicators (asterisks)
Progress indicator showing completion percentage
Ability to save progress and resume later
Form auto-save every 30 seconds to browser storage
Clear validation error messages in red text
Success messages in green for completed sections
Display credit terms and conditions in expandable accordion
Display privacy policy link
"I agree" checkbox for terms (required to submit)
"I consent to credit check" checkbox (required to submit)
Electronic signature field (typed name confirmation)
"Review Application" button navigates to review page showing all entered data
"Edit" links on review page to return to specific sections
"Submit Application" button on review page (only active after all validations pass)
Loading spinner during submission
Submission sends data to backend API endpoint (POST /api/applications)
Application data stored in database with timestamp and unique application ID
Success page displays after submission with application reference number
Confirmation email sent to applicant's email address
Expected timeline for decision displayed (e.g., "Decision in 5-7 business days")
Option to download/print application summary PDF
"Apply for Another Card" button returns to card comparison page
All sensitive data (SSN, Tax ID) encrypted in transit (HTTPS)
Sensitive fields masked (show last 4 digits only)
CSRF token protection on form submission
Rate limiting on application submissions (max 3 per day per IP)
Session timeout after 30 minutes of inactivity with warning at 25 minutes
Compliance with CCPA/GDPR data handling requirements
Application form fully functional on mobile devices (375px width)
Touch-friendly input fields and buttons
Mobile-optimized dropdowns and date pickers
Form sections collapse/expand appropriately on small screens
All form fields have proper labels and ARIA attributes
Keyboard navigation supported throughout form
Screen reader announcements for validation errors
Color contrast ratios meet WCAG standards
Focus indicators visible on all interactive elements
New database table BusinessCreditCardApplication with all form fields
REST endpoint POST /api/applications accepts application JSON
Input valid...
Work item: AB#859
Created via Azure DevOps