@extends('layouts.app') @section('title', 'NMI Payment Links') @section('body_class', 'nmi-admin-page') @push('styles') @endpush @section('content')

NMI Payment Links

Send a secure NMI payment link by SMS, email, or both. Enter at least a phone number or an email address, plus the amount; tax is calculated automatically and the customer pays with 3D Secure on the hosted NMI checkout.

@if(auth()->user()?->isAdmin())

NMI keys, tax, link expiry, and layaway options are in Setting → NMI Payment. @if(!$settings->isConfigured()) Add both NMI keys and enable the feature there before sending links. @endif

@elseif(!$settings->isConfigured())

NMI Payment Links is not configured yet. Ask an admin to set keys in Setting → NMI Payment.

@endif @if(auth()->user()?->isAdmin()) @php $overviewFilter = $overviewFilter ?? ['year' => (int) date('Y'), 'month' => (int) date('n'), 'label' => '']; $overviewSeries = $overviewSeries ?? []; $overviewMonth = $overviewMonth ?? []; $overviewYears = $overviewYears ?? []; $overviewMonths = $overviewMonths ?? []; $selectedOverviewPoint = collect($overviewSeries)->firstWhere('month', (int) ($overviewFilter['month'] ?? 0)) ?? [ 'amount' => 0, 'count' => 0, ]; @endphp
@foreach(request()->except(['year', 'month', 'page']) as $key => $value) @if(is_scalar($value)) @endif @endforeach

Paid collections only (net of refunds) · America/New_York

NMI collections by month · {{ $overviewFilter['year'] }}

Payment links · Layaway (card + cash) · Instagram Story

Year total ${{ number_format((float) ($overviewYearTotalAmount ?? 0), 2) }}
Year tax ${{ number_format((float) ($overviewYearTaxAmount ?? 0), 2) }}
Payments {{ (int) ($overviewYearTotalCount ?? 0) }}
{{ $overviewFilter['label'] ?? '' }} ${{ number_format((float) ($selectedOverviewPoint['amount'] ?? 0), 2) }}
@forelse($overviewSeries as $point) @empty @endforelse
Month # Amount Tax
{{ $point['label_full'] }} {{ $point['count'] }} ${{ number_format((float) $point['amount'], 2) }} ${{ number_format((float) ($point['tax_amount'] ?? 0), 2) }}
No paid NMI collections yet this year.
Layaway ${{ number_format((float) ($overviewYearLayawayAmount ?? 0), 2) }}
Instagram Story ${{ number_format((float) ($overviewYearStoryAmount ?? 0), 2) }}
Collected · {{ $overviewMonth['label'] ?? '' }} ${{ number_format((float) ($overviewMonth['collected'] ?? 0), 2) }} {{ (int) ($overviewMonth['paid_count'] ?? 0) }} paid · subtotal ${{ number_format((float) ($overviewMonth['subtotal_collected'] ?? 0), 2) }}
Tax collected · {{ $overviewMonth['label'] ?? '' }} ${{ number_format((float) ($overviewMonth['tax_collected'] ?? 0), 2) }} Sales tax on paid collections (net of refunds)
Payment links ${{ number_format((float) ($overviewMonth['payment_link']['amount'] ?? 0), 2) }} {{ (int) ($overviewMonth['payment_link']['count'] ?? 0) }} payments this month
Layaway collected ${{ number_format((float) ($overviewMonth['layaway']['amount'] ?? 0), 2) }} {{ (int) ($overviewMonth['layaway']['count'] ?? 0) }} payments @if((int) ($overviewMonth['cash_count'] ?? 0) > 0) · cash ${{ number_format((float) ($overviewMonth['cash_amount'] ?? 0), 2) }} @endif
Instagram Story ${{ number_format((float) ($overviewMonth['story']['amount'] ?? 0), 2) }} {{ (int) ($overviewMonth['story']['count'] ?? 0) }} payments (full + layaway)
Link conversion @if(isset($overviewMonth['link_conversion_percent']) && $overviewMonth['link_conversion_percent'] !== null) {{ number_format((float) $overviewMonth['link_conversion_percent'], 1) }}% @else — @endif {{ (int) ($overviewMonth['links_paid'] ?? 0) }}/{{ (int) ($overviewMonth['links_created'] ?? 0) }} paid · {{ (int) ($overviewMonth['links_pending'] ?? 0) }} pending · {{ (int) ($overviewMonth['links_expired'] ?? 0) }} expired
Layaway plans · month {{ (int) ($overviewMonth['plans_started'] ?? 0) }} started {{ (int) ($overviewMonth['plans_invited'] ?? 0) }} invited ({{ (int) ($overviewMonth['plans_invited_manual'] ?? 0) }} manual · {{ (int) ($overviewMonth['plans_invited_story'] ?? 0) }} story) · {{ (int) ($overviewMonth['plans_completed'] ?? 0) }} completed
Active layaways (now) {{ (int) ($overviewMonth['active_plans_count'] ?? 0) }} ${{ number_format((float) ($overviewMonth['active_plans_paid'] ?? 0), 2) }} paid · ${{ number_format((float) ($overviewMonth['active_plans_balance'] ?? 0), 2) }} still due
@endif

Send Payment Link

Enter customer contact, sale amount, and options — then send a secure hosted checkout link.

@csrf @php $nmiLinkSalespeople = $nmiLinkSalespeople ?? collect(); $nmiLayawaySalespeople = $nmiLayawaySalespeople ?? collect(); $nmiHasLinkSalespeople = $nmiLinkSalespeople->isNotEmpty(); $nmiHasLayawaySalespeople = $nmiLayawaySalespeople->isNotEmpty(); @endphp
Customer contact
@if($errors->has('phone'))

{{ $errors->first('phone') }}

@endif
@if($errors->has('email'))

{{ $errors->first('email') }}

@endif

US phone (10 digits or +1) and/or email — at least one is required.

Sale
@if($nmiHasLinkSalespeople || $nmiHasLayawaySalespeople)
@if($errors->has('salesperson_id'))

{{ $errors->first('salesperson_id') }}

@endif
@endif
@if($nmiHasLinkSalespeople || $nmiHasLayawaySalespeople)

Commission on paid link or first layaway payment. Sales commissions

@endif
Options

Uncheck for out-of-state (no tax).

@if($settings->isLayawayEnabled())

Opens layaway wizard instead of one-time link.

@endif
@php $layawayFiltersActive = !empty($filters['phone']) || !empty($filters['name']) || !empty($filters['email']) || !empty($filters['subtotal']) || !empty($filters['hide_story']) || !empty($filters['order_number']) || !empty($filters['plan_status']) || !empty($filters['payment_status']); $paymentFiltersActive = !empty($filters['phone']) || !empty($filters['name']) || !empty($filters['email']) || !empty($filters['subtotal']) || !empty($filters['hide_story']) || !empty($filters['payment_status']); @endphp @php $nmiTabQuery = request()->only(['phone', 'name', 'email', 'subtotal', 'hide_story', 'order_number', 'plan_status', 'payment_status', 'year', 'month']); @endphp
Payment links @if($settings->isLayawayEnabled()) Layaway plans @endif Activity history
@if(($activeTab ?? 'payments') === 'history')

Activity history

Recent notifications, payments, declines, refunds, voids, and CCAF events. Use Open to jump straight to the payment or layaway plan.

@if(empty($activityFeed))

No recent activity yet. Sends, reminders, payments, declines, refunds, voids, and CCAF updates will appear here.

@else @endif
@elseif(($activeTab ?? 'payments') === 'layaway' && $settings->isLayawayEnabled())

Layaway plans

Track plan totals, balances, and payments. Send balance reminders manually (email + SMS when a phone is on file). Weekly reminders and layaway terms are configured in Setting → NMI Payment.

@if($layawayFiltersActive) Reset @endif
@if(($layawayPlans ?? collect())->isEmpty())

{{ $layawayFiltersActive ? 'No layaway plans match your filters.' : 'No layaway plans yet.' }}

@else
@foreach($layawayPlans as $plan) @php $planPayments = $plan->paymentRequests; $paymentCount = $planPayments->count(); $planCustomerName = $plan->displayCustomerName(); $planCustomerEmail = $plan->displayCustomerEmail(); $planPhone = $plan->displayPhone(); @endphp @if($paymentCount > 0) @endif @endforeach
Image Created Customer Salesperson Subtotal Tax Total Paid Balance Payments SMS delivery Email delivery Plan status Plan expires Actions
@if($plan->productImageUrl()) @elseif($plan->needsManualProductImage()) @else @endif @if($plan->created_at) @php $at = $plan->created_at->timezone('America/New_York'); @endphp {{ $at->format('n/j/y') }} {{ $at->format('g:i A') }} @endif @if($plan->formattedOrderNumber()) Order #{{ $plan->formattedOrderNumber() }} @elseif($plan->needsManualTicketNumber()) @endif @if($planCustomerName) {{ $planCustomerName }} @endif @if($planCustomerEmail) {{ $planCustomerEmail }} @endif @if($planPhone) {{ $planPhone }} @endif @if($plan->source === 'story_purchase')Story@endif {{ $plan->salesperson?->full_name ?? '—' }} ${{ number_format($plan->subtotal, 2) }} @if($plan->hasTax())${{ number_format($plan->tax_amount, 2) }}@else—@endif ${{ number_format($plan->total_amount, 2) }} ${{ number_format($plan->amount_paid, 2) }} ${{ number_format($plan->balance_due, 2) }} @if($paymentCount === 0) None yet @else @endif
invitationSmsDeliveryTitle()) title="{{ $smsDeliveryTitle }}"@endif>{{ $plan->invitationSmsDeliveryLabel() }} @if($plan->hasInvitationSmsLinkViewed()) invitationSmsLinkViewedTitle()) title="{{ $smsTitle }}"@endif>Viewed @endif
@if($plan->last_reminder_sms_status === 'sent' && $plan->lastReminderLabel()) Reminder {{ $plan->lastReminderLabel() }} @endif
invitationEmailDeliveryTitle()) title="{{ $emailTitle }}"@endif>{{ $plan->invitationEmailDeliveryLabel() }} @if($plan->lastReminderLabel()) Reminder {{ $plan->lastReminderLabel() }} @endif {{ $plan->statusLabel() }} @if($plan->plan_expires_at) {{ $plan->plan_expires_at->timezone('America/New_York')->format('n/j/y g:i A') }} @elseif($plan->invitation_expires_at && in_array($plan->status, ['pending_first_payment', 'invitation_expired'], true)) Invite: {{ $plan->invitation_expires_at->timezone('America/New_York')->format('n/j/y g:i A') }} @else — @endif
Open @if($plan->canReactivateExpiredInvitation())
@csrf
@endif @if($plan->canEditPlanTotal()) @endif @if($plan->canRecordCashPayment()) @endif @if(($isNmiAdmin ?? false) && $plan->canCancelAndRefund()) @endif @if($plan->status === 'active' && $plan->balance_due > 0)
@csrf
@endif
@endif
@csrf

Record cash payment

Remaining balance: $0.00

Eastern time. Defaults to now; you can change it if needed.

@csrf @method('PUT')

Edit cash payment

Maximum allowed: $0.00

Eastern time.

@csrf

Edit layaway details

Ticket number and product image can be saved separately. Notes are optional.

JPG, PNG, GIF or WebP · max 5 MB. Uploading a new image replaces the current one.

Internal details about the product, such as size, gold color, watch color, or any other important specification.

@include('nmi_payment.partials.edit-salesperson-field', [ 'isNmiAdmin' => $isNmiAdmin ?? false, 'salespeople' => $nmiLayawaySalespeople ?? collect(), 'selectId' => 'nmi_layaway_plan_salesperson_id', 'fieldId' => 'nmi_layaway_plan_salesperson_field', ])
@csrf @method('PUT')

Edit layaway plan total

Enter the new item price before tax — same as when you send a layaway link. The breakdown below shows what is left on the item and the tax on that remainder.

Sales tax setting from when this layaway link was sent.

Item subtotal (before tax) $0.00
Plan total (with tax) $0.00
Paid total $0.00
To collect (incl. tax) $0.00

Product image

@else

Recent payment links

@if($hasPrivateKey && !empty($nmiLastSyncedAt))

Last synced from NMI: {{ $nmiLastSyncedAt->timezone('America/New_York')->format('M j, Y g:i A T') }}

@endif

Delivery and payment badges update automatically every 45 seconds — no page refresh needed.

@if($hasPrivateKey)
@csrf @include('nmi_payment.partials.list-context-hidden-fields')
@endif
@if($paymentFiltersActive) Reset @endif
@if($requests->isEmpty())

{{ $paymentFiltersActive ? 'No payment links match your filters.' : 'No payment links sent yet.' }}

@else
@if(auth()->user()?->isAdmin()) @endif @foreach($requests as $item) @php $item->markExpiredIfNeeded(); @endphp @if(auth()->user()?->isAdmin()) @endif @endforeach
Date Phone Salesperson Subtotal Tax Total SMS delivery Email delivery Payment 3DS CCAF Ready to shipSKUActions
@php $listAt = $item->listDisplayAt()?->timezone('America/New_York'); @endphp @if($listAt) {{ $listAt->format('n/j/y') }} {{ $listAt->format('g:i A') }} @else — @endif @if($item->isLayawayPayment()) Layaway @elseif($item->showStoryPendingLabel()) Story @else @if($item->displayPhone()) {{ $item->displayPhone() }} @endif @if($item->customer_name) {{ $item->customer_name }} @endif @if($item->customer_email) {{ $item->customer_email }} @endif @endif {{ $item->salesperson?->full_name ?? '—' }} ${{ number_format($item->subtotal, 2) }} @if($item->hasTax())${{ number_format($item->tax_amount, 2) }}@else—@endif ${{ number_format($item->total_amount, 2) }}
smsDeliveryTitle()) title="{{ $smsDeliveryTitle }}"@endif>{{ $item->smsDeliveryLabel() }} @if($item->hasSmsLinkViewed()) smsLinkViewedTitle()) title="{{ $smsTitle }}"@endif>Viewed @endif
emailDeliveryTitle()) title="{{ $title }}"@endif>{{ $item->emailDeliveryLabel() }} declineDetailTitle()) title="{{ $declineTitle }}"@endif>{{ $item->paymentStatusLabel() }} @include('nmi_payment.partials.decline-info-btn', ['payment' => $item]) @if($itemExpiresLabel = $item->pendingExpiresLabel()) {{ $itemExpiresLabel }} @endif @if($item->payment_status === 'paid' && $item->nmi_transaction_id) #{{ $item->nmi_transaction_id }} @if($item->hasDeclineHistory() && ($declineCount = count($item->declineAttemptsList())) > 0) {{ $declineCount }} prior decline{{ $declineCount === 1 ? '' : 's' }} @endif @elseif($item->payment_status === 'refunded' && $item->nmi_transaction_id) #{{ $item->nmi_transaction_id }} @if($item->nmi_refund_transaction_id) ref #{{ $item->nmi_refund_transaction_id }} @endif @elseif($item->payment_status === 'voided' && $item->nmi_transaction_id) #{{ $item->nmi_transaction_id }} @elseif($item->hasOpenDecline() && $item->nmi_decline_transaction_id) decl #{{ $item->nmi_decline_transaction_id }} @if(($declineCount = count($item->declineAttemptsList())) > 1) {{ $declineCount }} tries @endif @if($item->hasHighRiskDifferentDeclineCards()) High risk @endif @endif @if($item->payment_status === 'paid') {{ $item->threeDsLabel() }} @else — @endif @include('nmi_payment.partials.ccaf-cell', [ 'payment' => $item, 'riskSummary' => ($item->ccaf_contract_id && isset(($ccafRiskSummaries ?? [])[$item->ccaf_contract_id])) ? ($ccafRiskSummaries ?? [])[$item->ccaf_contract_id] : null, ]) @if($item->payment_status === 'paid' && $item->ccaf_status === 'signed') {{ $item->readyToShipLabel() }} @else — @endif @if($item->nmi_product_sku) {{ $item->nmi_product_sku }} @else — @endif @if($item->payment_url || $item->canViewNmiReceipt() || $item->canViewNmiRefundReceipt() || $item->canEditPendingDeliveryDetails() || (($isNmiAdmin ?? false) && $item->canCancelAndRefund()))
@if($item->payment_url) Open @endif @if($item->canEditPendingDeliveryDetails()) @endif @if($item->canViewNmiReceipt()) @include('nmi_payment.partials.receipt-pdf-btn', ['paymentRequest' => $item]) @elseif($item->canViewNmiRefundReceipt()) @include('nmi_payment.partials.refund-receipt-pdf-btn', ['paymentRequest' => $item]) @endif @if(($isNmiAdmin ?? false) && $item->canCancelAndRefund()) @endif @if($item->payment_url) @if($item->canReactivateExpiredLink())
@csrf @include('nmi_payment.partials.list-context-hidden-fields')
@endif @if($item->canNotifyCustomer())
@csrf @include('nmi_payment.partials.list-context-hidden-fields')
@endif @endif
@else — @endif
{{ $requests->links('vendor.pagination.tailwind') }} @endif
@endif
@if($isNmiAdmin ?? false)
@csrf @include('nmi_payment.partials.list-context-hidden-fields')

Cancel & refund

Review amounts, then confirm. Card payments are voided (unsettled) or refunded (settled) in NMI.

@endif

Declined payment details

@if(auth()->user()?->isAdmin()) @endif @include('nmi_payment.partials.ccaf-risk-modal') @endsection