Compare commits

..

43 Commits

Author SHA1 Message Date
4e58905bf7 migrate
All checks were successful
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 15s
Build, Push and Deploy / build-and-push (push) Successful in 3m0s
Build, Push and Deploy / deploy-staging (push) Successful in 36s
Build, Push and Deploy / deploy-production (push) Has been skipped
2026-04-12 23:49:02 +07:00
2e2b0ee178 test migrate 2026-04-12 23:46:56 +07:00
eko
d9988d59a4 fix entrypoint
All checks were successful
Build, Push and Deploy / build-and-push (push) Successful in 2m56s
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 15s
Build, Push and Deploy / deploy-staging (push) Successful in 25s
Build, Push and Deploy / deploy-production (push) Has been skipped
2026-04-12 22:38:52 +07:00
eko
afc31bc825 fix entrypoint
All checks were successful
Build, Push and Deploy / build-and-push (push) Successful in 3m25s
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 17s
Build, Push and Deploy / deploy-staging (push) Successful in 29s
Build, Push and Deploy / deploy-production (push) Has been skipped
2026-04-12 22:23:56 +07:00
eko
ac2ec6e659 add swagger
All checks were successful
Build, Push and Deploy / build-and-push (push) Successful in 3m28s
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 15s
Build, Push and Deploy / deploy-staging (push) Successful in 38s
Build, Push and Deploy / deploy-production (push) Has been skipped
2026-04-12 22:06:46 +07:00
eko
75ece668cc fix vald
All checks were successful
Build, Push and Deploy / build-and-push (push) Successful in 3m2s
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 14s
Build, Push and Deploy / deploy-staging (push) Successful in 35s
Build, Push and Deploy / deploy-production (push) Has been skipped
2026-04-05 00:57:34 +07:00
eko
eafa8d362f fix vald
All checks were successful
Build, Push and Deploy / build-and-push (push) Successful in 3m3s
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 14s
Build, Push and Deploy / deploy-staging (push) Successful in 36s
Build, Push and Deploy / deploy-production (push) Has been skipped
Build, Push and Deploy / cleanup (push) Successful in 0s
2026-04-05 00:39:10 +07:00
eko
2d2cb95600 fix val
All checks were successful
Build, Push and Deploy / build-and-push (push) Successful in 3m0s
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 14s
Build, Push and Deploy / deploy-staging (push) Successful in 36s
Build, Push and Deploy / deploy-production (push) Has been skipped
Build, Push and Deploy / cleanup (push) Successful in 1s
2026-04-05 00:18:48 +07:00
eko
1d63c28356 remove route::cache production
All checks were successful
Build, Push and Deploy / build-and-push (push) Successful in 3m12s
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 14s
Build, Push and Deploy / deploy-staging (push) Successful in 37s
Build, Push and Deploy / deploy-production (push) Has been skipped
Build, Push and Deploy / cleanup (push) Successful in 0s
2026-04-04 23:51:47 +07:00
eko
4398aee449 fix production
All checks were successful
Build, Push and Deploy / build-and-push (push) Successful in 3m9s
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 15s
Build, Push and Deploy / deploy-staging (push) Successful in 38s
Build, Push and Deploy / deploy-production (push) Has been skipped
Build, Push and Deploy / cleanup (push) Successful in 1s
2026-04-04 23:34:45 +07:00
eko
481b8fba7e fix pipeline production still use port 8000
All checks were successful
Build, Push and Deploy / build-and-push (push) Successful in 3m15s
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 16s
Build, Push and Deploy / deploy-staging (push) Successful in 39s
Build, Push and Deploy / deploy-production (push) Has been skipped
Build, Push and Deploy / cleanup (push) Successful in 1s
2026-04-04 23:12:57 +07:00
cdd7fbaecb csrf
All checks were successful
Build, Push and Deploy / build-and-push (push) Successful in 3m10s
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 14s
Build, Push and Deploy / deploy-staging (push) Successful in 41s
Build, Push and Deploy / deploy-production (push) Has been skipped
Build, Push and Deploy / cleanup (push) Successful in 1s
2026-04-03 21:06:42 +07:00
ff60e63ece crsf bugs
All checks were successful
Build, Push and Deploy / build-and-push (push) Successful in 3m37s
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 14s
Build, Push and Deploy / deploy-staging (push) Successful in 37s
Build, Push and Deploy / deploy-production (push) Has been skipped
Build, Push and Deploy / cleanup (push) Successful in 1s
2026-04-03 20:41:20 +07:00
e5d8df803f hapus
Some checks failed
Build, Push and Deploy / build-and-push (push) Failing after 2m58s
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 15s
Build, Push and Deploy / deploy-staging (push) Has been skipped
Build, Push and Deploy / deploy-production (push) Has been skipped
Build, Push and Deploy / cleanup (push) Successful in 0s
2026-04-02 23:20:02 +07:00
df323c1c70 gdockerignore cache
Some checks failed
Build, Push and Deploy / build-and-push (push) Failing after 3m10s
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 13s
Build, Push and Deploy / deploy-staging (push) Has been skipped
Build, Push and Deploy / deploy-production (push) Has been skipped
Build, Push and Deploy / cleanup (push) Successful in 0s
2026-04-02 23:12:48 +07:00
c0d1714543 docker ignore cache
Some checks failed
Build, Push and Deploy / build-and-push (push) Failing after 3m24s
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 15s
Build, Push and Deploy / deploy-staging (push) Has been skipped
Build, Push and Deploy / deploy-production (push) Has been skipped
Build, Push and Deploy / cleanup (push) Successful in 1s
2026-04-02 23:05:21 +07:00
2f17c3abdd update pull
All checks were successful
Build, Push and Deploy / build-and-push (push) Successful in 3m13s
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 15s
Build, Push and Deploy / deploy-staging (push) Successful in 37s
Build, Push and Deploy / deploy-production (push) Has been skipped
Build, Push and Deploy / cleanup (push) Successful in 0s
2026-04-02 22:00:30 +07:00
ee73c81dde Merge branch 'main' of https://kode.sadateknologi.site/yanto/kulakpos_web 2026-04-02 21:39:22 +07:00
78dd794f71 pulldata 2026-04-02 21:38:46 +07:00
triagungbiantoro
1ab56d68b3 perbaikan tombol e-wallet
All checks were successful
Build, Push and Deploy / build-and-push (push) Successful in 3m59s
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 15s
Build, Push and Deploy / deploy-staging (push) Successful in 38s
Build, Push and Deploy / deploy-production (push) Has been skipped
Build, Push and Deploy / cleanup (push) Successful in 1s
2026-04-02 21:26:01 +07:00
triagungbiantoro
ac43e83b38 Perbaikan Button Pilih Basic dan Pilih Pro
All checks were successful
Build, Push and Deploy / build-and-push (push) Successful in 3m13s
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 15s
Build, Push and Deploy / deploy-staging (push) Successful in 43s
Build, Push and Deploy / deploy-production (push) Has been skipped
Build, Push and Deploy / cleanup (push) Successful in 1s
2026-04-02 09:12:29 +07:00
triagungbiantoro
80f4d8f046 perbaikan mobile view + no Whatsapp
All checks were successful
Build, Push and Deploy / build-and-push (push) Successful in 3m57s
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 15s
Build, Push and Deploy / deploy-staging (push) Successful in 38s
Build, Push and Deploy / deploy-production (push) Has been skipped
Build, Push and Deploy / cleanup (push) Successful in 1s
2026-04-01 18:06:09 +07:00
triagungbiantoro
8fafbccb66 ganti logo dan link tombol Jadwalkan Demo
All checks were successful
Build, Push and Deploy / build-and-push (push) Successful in 3m47s
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 15s
Build, Push and Deploy / deploy-staging (push) Successful in 40s
Build, Push and Deploy / deploy-production (push) Has been skipped
Build, Push and Deploy / cleanup (push) Successful in 1s
2026-04-01 09:19:43 +07:00
f2c19ec025 update git
All checks were successful
Build, Push and Deploy / build-and-push (push) Successful in 3m8s
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 15s
Build, Push and Deploy / deploy-staging (push) Successful in 26s
Build, Push and Deploy / deploy-production (push) Has been skipped
Build, Push and Deploy / cleanup (push) Successful in 0s
2026-03-31 08:27:42 +07:00
8fb1995913 folder
All checks were successful
Build, Push and Deploy / build-and-push (push) Successful in 2m38s
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 16s
Build, Push and Deploy / deploy-staging (push) Successful in 31s
Build, Push and Deploy / deploy-production (push) Has been skipped
Build, Push and Deploy / cleanup (push) Successful in 0s
2026-03-31 01:32:38 +07:00
d346b24299 update docker file
Some checks failed
Build, Push and Deploy / build-and-push (push) Failing after 2m0s
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 16s
Build, Push and Deploy / deploy-staging (push) Has been skipped
Build, Push and Deploy / deploy-production (push) Has been skipped
Build, Push and Deploy / cleanup (push) Successful in 1s
2026-03-31 01:25:48 +07:00
1c730d154d test second dockeerfile
Some checks failed
Build, Push and Deploy / build-and-push (push) Failing after 1m55s
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 14s
Build, Push and Deploy / deploy-staging (push) Has been skipped
Build, Push and Deploy / deploy-production (push) Has been skipped
Build, Push and Deploy / cleanup (push) Successful in 1s
2026-03-31 01:17:24 +07:00
94a70c95c0 remove composer lock
All checks were successful
Build, Push and Deploy / build-and-push (push) Successful in 2m47s
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 15s
Build, Push and Deploy / deploy-staging (push) Successful in 28s
Build, Push and Deploy / deploy-production (push) Has been skipped
Build, Push and Deploy / cleanup (push) Successful in 1s
2026-03-30 18:07:58 +07:00
a5638095f1 update git
All checks were successful
Build, Push and Deploy / build-and-push (push) Successful in 2m40s
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 15s
Build, Push and Deploy / deploy-staging (push) Successful in 29s
Build, Push and Deploy / deploy-production (push) Has been skipped
Build, Push and Deploy / cleanup (push) Successful in 1s
2026-03-30 17:50:31 +07:00
b5e3a778ce allow vendord
All checks were successful
Build, Push and Deploy / build-and-push (push) Successful in 3m3s
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 16s
Build, Push and Deploy / deploy-staging (push) Successful in 32s
Build, Push and Deploy / deploy-production (push) Has been skipped
Build, Push and Deploy / cleanup (push) Successful in 1s
2026-03-30 14:54:57 +07:00
66aed7c4e8 update bug docker file
All checks were successful
Build, Push and Deploy / build-and-push (push) Successful in 3m7s
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 8s
Build, Push and Deploy / deploy-staging (push) Successful in 37s
Build, Push and Deploy / deploy-production (push) Has been skipped
Build, Push and Deploy / cleanup (push) Successful in 0s
2026-03-30 14:32:56 +07:00
327dceee4e update bugs dockerfile
All checks were successful
Build, Push and Deploy / build-and-push (push) Successful in 3m27s
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 9s
Build, Push and Deploy / deploy-staging (push) Successful in 37s
Build, Push and Deploy / deploy-production (push) Has been skipped
Build, Push and Deploy / cleanup (push) Successful in 1s
2026-03-30 14:18:41 +07:00
eko
d722295036 fix /plan
All checks were successful
Build, Push and Deploy / build-and-push (push) Successful in 2m57s
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 7s
Build, Push and Deploy / deploy-staging (push) Successful in 32s
Build, Push and Deploy / deploy-production (push) Has been skipped
Build, Push and Deploy / cleanup (push) Successful in 1s
2026-03-29 19:35:21 +07:00
eko
74e6666da5 fix docker
All checks were successful
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 8s
Build, Push and Deploy / build-and-push (push) Successful in 2m39s
Build, Push and Deploy / deploy-staging (push) Successful in 22s
Build, Push and Deploy / deploy-production (push) Has been skipped
Build, Push and Deploy / cleanup (push) Successful in 1s
2026-03-29 15:42:51 +07:00
eko
a234bc497e fix docker
Some checks failed
Build, Push and Deploy / build-and-push (push) Failing after 1m56s
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 8s
Build, Push and Deploy / deploy-staging (push) Has been skipped
Build, Push and Deploy / deploy-production (push) Has been skipped
Build, Push and Deploy / cleanup (push) Successful in 0s
2026-03-29 15:33:07 +07:00
eko
957352e257 fix docker
All checks were successful
Build, Push and Deploy / build-and-push (push) Successful in 2m35s
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 9s
Build, Push and Deploy / deploy-staging (push) Successful in 21s
Build, Push and Deploy / deploy-production (push) Has been skipped
Build, Push and Deploy / cleanup (push) Successful in 1s
2026-03-29 15:12:24 +07:00
eko
6b34fcb87a fix docker
All checks were successful
Build, Push and Deploy / build-and-push (push) Successful in 2m36s
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 8s
Build, Push and Deploy / deploy-staging (push) Successful in 30s
Build, Push and Deploy / deploy-production (push) Has been skipped
Build, Push and Deploy / cleanup (push) Successful in 0s
2026-03-29 14:53:39 +07:00
eko
46b1a4f74d fix docker
Some checks failed
Build, Push and Deploy / build-and-push (push) Failing after 1m51s
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 9s
Build, Push and Deploy / deploy-staging (push) Has been skipped
Build, Push and Deploy / deploy-production (push) Has been skipped
Build, Push and Deploy / cleanup (push) Successful in 1s
2026-03-29 14:45:23 +07:00
eko
b0a929684a fix dockerfile
Some checks failed
Build, Push and Deploy / build-and-push (push) Failing after 1m52s
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 8s
Build, Push and Deploy / deploy-staging (push) Has been skipped
Build, Push and Deploy / deploy-production (push) Has been skipped
Build, Push and Deploy / cleanup (push) Successful in 1s
2026-03-29 14:37:23 +07:00
eko
30a3f05cbb fix dockerfile
Some checks failed
Build, Push and Deploy / build-and-push (push) Failing after 1m49s
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 8s
Build, Push and Deploy / deploy-staging (push) Has been skipped
Build, Push and Deploy / deploy-production (push) Has been skipped
Build, Push and Deploy / cleanup (push) Successful in 0s
2026-03-29 14:32:03 +07:00
eko
0be65d8013 adapt version
Some checks failed
Build, Push and Deploy / build-and-push (push) Successful in 2m28s
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 8s
Build, Push and Deploy / deploy-staging (push) Successful in 28s
Build, Push and Deploy / deploy-production (push) Has been skipped
Build, Push and Deploy / cleanup (push) Successful in 0s
2026-03-29 14:11:26 +07:00
eko
bd85604fb4 using vite: ^8.0.3
Some checks failed
Build, Push and Deploy / build-and-push (push) Failing after 1m40s
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 8s
Build, Push and Deploy / deploy-staging (push) Has been skipped
Build, Push and Deploy / deploy-production (push) Has been skipped
Build, Push and Deploy / cleanup (push) Successful in 0s
2026-03-29 14:01:39 +07:00
eko
f1b383fbc7 add vite
Some checks failed
Build, Push and Deploy / build-and-push (push) Failing after 1m33s
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 8s
Build, Push and Deploy / deploy-staging (push) Has been skipped
Build, Push and Deploy / deploy-production (push) Has been skipped
Build, Push and Deploy / cleanup (push) Successful in 1s
2026-03-29 13:48:35 +07:00
21340 changed files with 2780251 additions and 919 deletions

30
.dockerignore Normal file
View File

@@ -0,0 +1,30 @@
# **/node_modules
# **/vendor
# #.env
# .env.backup
# .git
# .phpunit.result.cache
# Homestead.json
# Homestead.yaml
# auth.json
# npm-debug.log
# yarn-error.log
# .idea
# .vscode
# storage/app/public
# storage/app/temp
# storage/app/uploads
# storage/debugbar
# storage/framework/views/*
# public/modules/*
# .qodo
# .camel-jbang
# # Ignore large archives that slow down docker build context
# *.zip
# *.tar.gz
# *.sqlstorage/framework/sessions/*
storage/framework/sessions/*
storage/framework/views/*
storage/framework/cache/*
bootstrap/cache/*

View File

@@ -84,7 +84,7 @@ jobs:
echo "Starting new container..."
docker run -d \
--restart unless-stopped \
--health-cmd="curl -f http://localhost:8000 || exit 1" \
--health-cmd="curl -f http://127.0.0.1:8000 || exit 1" \
--health-interval=30s \
--health-timeout=10s \
--health-retries=3 \
@@ -98,7 +98,7 @@ jobs:
# Check if container is running
sleep 5
if docker ps | grep -q kulakpos_web_staging; then
if docker ps | grep kulakpos_web_staging; then
echo "✅ Staging deployment successful!"
docker logs --tail 20 kulakpos_web_staging
else
@@ -183,7 +183,13 @@ jobs:
docker ps --filter name=kulakpos_web_prod
# Create backup of current deployment info
docker inspect kulakpos_web_prod > /tmp/kulakpos_backup_$(date +%Y%m%d_%H%M%S).json || true
mkdir -p /opt/kulakpos/backups
docker inspect kulakpos_web_prod > /opt/kulakpos/backups/kulakpos_backup_$(date +%Y%m%d_%H%M%S).json || true
# Backup .env file
if [ -f "/opt/kulakpos/.env" ]; then
cp /opt/kulakpos/.env /opt/kulakpos/backups/.env_backup_$(date +%Y%m%d_%H%M%S)
fi
# Pull the specified image
echo "Pulling image: kode.sadateknologi.site/${{ gitea.repository_owner }}/kulakpos_web:$IMAGE_TAG"
@@ -194,11 +200,11 @@ jobs:
docker stop kulakpos_web_prod || true
docker rm kulakpos_web_prod || true
# Run new container
echo "Starting new container..."
# Run new container (using PHP's built-in server on port 8000 like staging)
echo "Starting new production container on port 8000..."
docker run -d \
--restart unless-stopped \
--health-cmd="pgrep php-fpm" \
--health-cmd="curl -f http://127.0.0.1:8000 || exit 1" \
--health-interval=30s \
--health-timeout=10s \
--health-start-period=40s \
@@ -206,15 +212,23 @@ jobs:
--name kulakpos_web_prod \
--network="host" \
-v /opt/kulakpos/.env:/var/www/html/.env \
-p 9000:9000 \
-e MODE=prod \
-e MODE=production \
-e APP_ENV=production \
-e APP_DEBUG=false \
-e AUTO_MIGRATE=false \
kode.sadateknologi.site/${{ gitea.repository_owner }}/kulakpos_web:$IMAGE_TAG
# Check if container is running
# Wait for container to be ready
echo "Waiting for container to be ready..."
sleep 10
if docker ps | grep -q kulakpos_web_prod; then
# Check if container is running
if docker ps | grep kulakpos_web_prod; then
echo "✅ Production deployment successful!"
echo ""
echo "Container status:"
docker ps --filter name=kulakpos_web_prod --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
echo ""
echo "Container logs (last 20 lines):"
docker logs --tail 20 kulakpos_web_prod
else
@@ -222,50 +236,31 @@ jobs:
docker logs kulakpos_web_prod || echo "No logs available"
# Attempt rollback to previous version if available
echo ""
echo "Attempting rollback to previous version..."
PREVIOUS_IMAGE=$(docker inspect kulakpos_web_prod --format='{{.Config.Image}}' 2>/dev/null || echo "")
if [ -n "$PREVIOUS_IMAGE" ] && [ "$PREVIOUS_IMAGE" != "kode.sadateknologi.site/${{ gitea.repository_owner }}/kulakpos_web:$IMAGE_TAG" ]; then
docker run -d \
--restart unless-stopped \
--name kulakpos_web_prod \
--network="host" \
-v /opt/kulakpos/.env:/var/www/html/.env \
-p 8000:8000 \
-e MODE=prod \
-e APP_ENV=production \
$PREVIOUS_IMAGE
echo "Rollback attempted"
PREVIOUS_BACKUP=$(ls -t /opt/kulakpos/backups/kulakpos_backup_*.json 2>/dev/null | head -1)
if [ -f "$PREVIOUS_BACKUP" ]; then
PREVIOUS_IMAGE=$(jq -r '.[0].Config.Image' $PREVIOUS_BACKUP 2>/dev/null || echo "")
if [ -n "$PREVIOUS_IMAGE" ] && [ "$PREVIOUS_IMAGE" != "kode.sadateknologi.site/${{ gitea.repository_owner }}/kulakpos_web:$IMAGE_TAG" ]; then
echo "Rolling back to: $PREVIOUS_IMAGE"
docker run -d \
--restart unless-stopped \
--health-cmd="curl -f http://127.0.0.1:8000 || exit 1" \
--health-interval=30s \
--health-timeout=10s \
--health-start-period=40s \
--health-retries=3 \
--name kulakpos_web_prod \
--network="host" \
-v /opt/kulakpos/.env:/var/www/html/.env \
-e MODE=production \
-e APP_ENV=production \
-e APP_DEBUG=false \
-e AUTO_MIGRATE=false \
$PREVIOUS_IMAGE
echo "Rollback completed"
fi
fi
exit 1
fi
EOF
# - name: Verify Production Deployment
# run: |
# ssh kulakpos_prod "curl -f http://localhost:8000/health || curl -f http://localhost:8000 || echo 'Service is running but health check endpoint not configured'"
# echo "✅ Production deployment verified!"
- name: Verify Production Deployment
run: |
ssh kulakpos_prod "docker ps --filter 'name=kulakpos_web_prod' --format '{{.Status}}' | grep -q healthy"
echo "✅ Production deployment verified!"
- name: Send Deployment Notification
if: always()
run: |
STATUS="${{ job.status }}"
if [ "$STATUS" = "success" ]; then
echo "✅ Production deployment completed successfully!"
else
echo "❌ Production deployment failed!"
exit 1
fi
cleanup:
runs-on: ubuntu-latest
needs: [deploy-staging, deploy-production]
if: always()
steps:
- name: Cleanup old Docker images on runners
run: |
docker system prune -f --filter "until=24h"

View File

@@ -25,7 +25,6 @@ jobs:
- run: head -n 5 app/Helpers/Helper.php
- run: ls -la app/Helpers/Helper.php
- run: file app/Helpers/Helper.php
- run: nl -ba app/Helpers/Helper.php | head -10
- run: pwd
- run: whoami

5
.gitignore vendored
View File

@@ -1,7 +1,7 @@
/node_modules
/public/build
/public/hot
/vendor
#/vendor
.env
.env.backup
.phpunit.result.cache
@@ -19,4 +19,5 @@ yarn-error.log
/storage/framework/views/*
/public/modules/*
.qodo
.camel-jbang
.camel-jbang
dump/

View File

@@ -12,46 +12,81 @@ RUN apk add --no-cache \
oniguruma-dev \
git \
curl \
nodejs \
npm \
&& docker-php-ext-configure gd --with-freetype --with-jpeg \
&& docker-php-ext-install -j$(nproc) \
pdo_pgsql \
pgsql \
opcache \
zip \
bcmath \
gd \
exif \
pcntl
pdo_pgsql \
pgsql \
opcache \
zip \
bcmath \
gd \
exif \
pcntl
# Configure git to allow safe directory
RUN git config --global --add safe.directory /var/www/html
# Install Composer
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
WORKDIR /var/www/html
# Copy application files
COPY --chown=www-data:www-data . .
# Copy ONLY composer files first
COPY composer.json ./
# Remove any nested vendor directories to avoid conflicts
RUN find . -type d -name "vendor" -not -path "./vendor*" -exec rm -rf {} + 2>/dev/null || true
# Create all necessary directories
RUN mkdir -p storage/framework/{cache,sessions,views} \
# Create storage and bootstrap directories needed for composer scripts
RUN mkdir -p storage/framework/cache \
&& mkdir -p storage/framework/sessions \
&& mkdir -p storage/framework/views \
&& mkdir -p storage/framework/testing \
&& mkdir -p storage/logs \
&& mkdir -p bootstrap/cache \
&& chmod -R 777 storage \
&& chmod -R 777 bootstrap/cache
# Install dependencies
RUN composer install --no-dev --no-interaction --no-progress --optimize-autoloader --no-scripts \
&& composer run-script post-autoload-dump \
&& php artisan package:discover --ansi || true
# Configure Composer for stability on unstable networks
ENV COMPOSER_ALLOW_SUPERUSER=1 \
COMPOSER_MEMORY_LIMIT=-1 \
COMPOSER_PROCESS_TIMEOUT=2000
# Generate application key
RUN php artisan key:generate --no-interaction --force || true
# Install PHP dependencies (this layer will be cached)
RUN composer config -g repo.packagist composer https://packagist.org \
&& composer install --no-interaction --no-progress --no-scripts --prefer-dist
# Isolate the clean vendor directory
#RUN mv vendor /vendor_clean
# Copy the rest of the application files (will ignore vendor because of .dockerignore)
COPY --chown=www-data:www-data . .
# Restore the clean vendor directory and set proper permissions
#RUN rm -rf vendor && mv /vendor_clean vendor && chown -R www-data:www-data vendor
# Final build verification
RUN if [ ! -f vendor/laravel/prompts/src/helpers.php ]; then \
echo "ERROR: laravel/prompts/src/helpers.php missing!" && exit 1; \
fi
# Final check and debug listing
RUN ls -l vendor/laravel/prompts/src/helpers.php \
&& du -sh vendor/laravel/prompts
# Copy package files for NPM
COPY package*.json ./
COPY vite.config.js ./
# Install NPM dependencies
RUN npm install --legacy-peer-deps --no-audit
# Build Vite assets
RUN npm run build
# Copy entrypoint script and ensure it's executable
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
# Let the entrypoint handle artisan commands at runtime
EXPOSE 8000 9000
ENTRYPOINT ["/entrypoint.sh"]

51
Dockerfile2 Normal file
View File

@@ -0,0 +1,51 @@
FROM php:8.4-fpm
# 1. Setup NodeSource untuk instalasi Node.js v20 (Cepat & Stabil)
RUN apt-get update && apt-get install -y ca-certificates curl gnupg \
&& curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg \
&& echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_20.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list
# 2. Install dependencies utama dengan apt-get
RUN apt-get update && apt-get install -y \
git \
libpng-dev \
libonig-dev \
libxml2-dev \
libpq-dev \
libfreetype6-dev \
libjpeg62-turbo-dev \
libzip-dev \
zip \
unzip \
nodejs \
&& apt-get clean && rm -rf /var/lib/apt/lists/*
# 3. Install PHP Extensions (TERMASUK pgsql / pdo_pgsql)
RUN docker-php-ext-configure gd --with-freetype --with-jpeg \
&& docker-php-ext-install pdo_mysql pdo_pgsql pgsql mbstring exif pcntl bcmath gd zip
# 4. Get latest Composer
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
WORKDIR /var/www
# 5. Copy existing application directory contents
COPY . /var/www
# 6. Install dependencies
# (Hapus --no-dev sementara jika Anda mau testing dengan Error Page Laravel yang rapi)
RUN composer install --no-interaction --optimize-autoloader
# 7. Install & Build Frontend
RUN npm install
RUN npm run build
# 8. Set permissions
RUN chown -R www-data:www-data /var/www/storage /var/www/bootstrap/cache \
&& chmod -R 775 /var/www/storage /var/www/bootstrap/cache
# 9. Expose port 8000 and start php artisan serve
EXPOSE 8000
# artisan serve berjalan langsung
CMD ["php", "artisan", "serve", "--host=0.0.0.0", "--port=8000"]

View File

@@ -68,3 +68,8 @@ ## Authenticate
email : admin@gmail.com <br>
pass : password
docker build -f Dockerfile2 -t kulakpos-app:latest .
docker run -d --name kulakpos-testing --network kulakpos-net -p 8000:8000 --env-file .env kulakpos-app:latest

View File

@@ -12,7 +12,7 @@ class TrustProxies extends Middleware
*
* @var array<int, string>|string|null
*/
protected $proxies;
protected $proxies = '*';
/**
* The headers that should be used to detect proxies.

253
build.log Normal file
View File

@@ -0,0 +1,253 @@
#0 building with "default" instance using docker driver
#1 [internal] load build definition from Dockerfile
#1 transferring dockerfile: 2.31kB done
#1 DONE 0.0s
#2 [internal] load metadata for docker.io/library/php:8.2-fpm-alpine
#2 ...
#3 [internal] load metadata for docker.io/library/composer:latest
#3 DONE 6.0s
#2 [internal] load metadata for docker.io/library/php:8.2-fpm-alpine
#2 DONE 6.6s
#4 [internal] load .dockerignore
#4 transferring context: 412B done
#4 DONE 0.0s
#5 [stage-0 1/17] FROM docker.io/library/php:8.2-fpm-alpine@sha256:6363baa3186e5bd43794c9612f8a34fb88657179de3da3ebc37b8618ba7bfffc
#5 DONE 0.0s
#6 FROM docker.io/library/composer:latest@sha256:743aebe48ca67097c36819040633ea77e44a561eca135e4fc84c002e63a1ba07
#6 DONE 0.0s
#7 [internal] load build context
#7 transferring context: 357.31kB 0.1s done
#7 DONE 0.1s
#8 [stage-0 3/17] RUN git config --global --add safe.directory /var/www/html
#8 CACHED
#9 [stage-0 4/17] COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
#9 CACHED
#10 [stage-0 5/17] WORKDIR /var/www/html
#10 CACHED
#11 [stage-0 6/17] COPY composer.json composer.lock ./
#11 CACHED
#12 [stage-0 2/17] RUN apk add --no-cache vim xxd libzip-dev postgresql-dev libpng-dev libjpeg-turbo-dev freetype-dev oniguruma-dev git curl nodejs npm && docker-php-ext-configure gd --with-freetype --with-jpeg && docker-php-ext-install -j$(nproc) pdo_pgsql pgsql opcache zip bcmath gd exif pcntl
#12 CACHED
#13 [stage-0 7/17] RUN mkdir -p storage/framework/cache && mkdir -p storage/framework/sessions && mkdir -p storage/framework/views && mkdir -p storage/framework/testing && mkdir -p storage/logs && mkdir -p bootstrap/cache && chmod -R 777 storage && chmod -R 777 bootstrap/cache
#13 CACHED
#14 [stage-0 8/17] RUN composer install --no-interaction --no-progress --no-scripts
#14 0.298 Installing dependencies from lock file (including require-dev)
#14 0.303 Verifying lock file contents can be installed on current platform.
#14 0.325 Package operations: 202 installs, 0 updates, 0 removals
#14 0.343 - Downloading php-http/discovery (1.20.0)
#14 0.343 - Downloading spatie/color (1.8.0)
#14 0.344 - Downloading voku/portable-ascii (2.0.3)
#14 0.344 - Downloading symfony/polyfill-php80 (v1.33.0)
#14 0.344 - Downloading symfony/polyfill-mbstring (v1.33.0)
#14 0.344 - Downloading symfony/polyfill-ctype (v1.33.0)
#14 0.344 - Downloading phpoption/phpoption (1.9.5)
#14 0.344 - Downloading graham-campbell/result-type (v1.1.4)
#14 0.344 - Downloading vlucas/phpdotenv (v5.6.3)
#14 0.344 - Downloading symfony/css-selector (v7.4.0)
#14 0.345 - Downloading tijsverkoyen/css-to-inline-styles (v2.4.0)
#14 0.345 - Downloading symfony/deprecation-contracts (v3.6.0)
#14 0.345 - Downloading symfony/var-dumper (v6.4.26)
#14 0.345 - Downloading symfony/polyfill-uuid (v1.33.0)
#14 0.345 - Downloading symfony/uid (v6.4.24)
#14 0.345 - Downloading symfony/routing (v6.4.30)
#14 0.345 - Downloading symfony/process (v6.4.31)
#14 0.345 - Downloading symfony/polyfill-intl-normalizer (v1.33.0)
#14 0.345 - Downloading symfony/polyfill-intl-idn (v1.33.0)
#14 0.345 - Downloading symfony/mime (v6.4.30)
#14 0.346 - Downloading psr/container (2.0.2)
#14 0.346 - Downloading symfony/service-contracts (v3.6.1)
#14 0.346 - Downloading psr/event-dispatcher (1.0.0)
#14 0.346 - Downloading symfony/event-dispatcher-contracts (v3.6.0)
#14 0.346 - Downloading symfony/event-dispatcher (v7.4.0)
#14 0.346 - Downloading psr/log (3.0.2)
#14 0.346 - Downloading doctrine/deprecations (1.1.5)
#14 0.346 - Downloading doctrine/lexer (2.1.1)
#14 0.346 - Downloading egulias/email-validator (4.0.4)
#14 0.346 - Downloading symfony/mailer (v6.4.31)
#14 0.346 - Downloading symfony/polyfill-php83 (v1.33.0)
#14 0.346 - Downloading symfony/http-foundation (v6.4.31)
#14 0.346 - Downloading symfony/error-handler (v6.4.26)
#14 0.346 - Downloading symfony/http-kernel (v6.4.31)
#14 0.347 - Downloading symfony/finder (v6.4.31)
#14 0.347 - Downloading symfony/polyfill-intl-grapheme (v1.33.0)
#14 0.347 - Downloading symfony/string (v7.4.0)
#14 0.347 - Downloading symfony/console (v6.4.31)
#14 0.347 - Downloading ramsey/collection (2.1.1)
#14 0.347 - Downloading brick/math (0.12.3)
#14 0.347 - Downloading ramsey/uuid (4.9.2)
#14 0.347 - Downloading psr/simple-cache (3.0.0)
#14 0.347 - Downloading nunomaduro/termwind (v1.17.0)
#14 0.347 - Downloading symfony/translation-contracts (v3.6.1)
#14 0.347 - Downloading symfony/translation (v6.4.31)
#14 0.347 - Downloading psr/clock (1.0.0)
#14 0.347 - Downloading carbonphp/carbon-doctrine-types (2.1.0)
#14 0.348 - Downloading nesbot/carbon (2.73.0)
#14 0.348 - Downloading monolog/monolog (3.10.0)
#14 0.348 - Downloading league/mime-type-detection (1.16.0)
#14 0.348 - Downloading league/flysystem (3.30.2)
#14 0.348 - Downloading league/flysystem-local (3.30.2)
#14 0.348 - Downloading nette/utils (v4.1.1)
#14 0.348 - Downloading nette/schema (v1.3.3)
#14 0.348 - Downloading dflydev/dot-access-data (v3.0.3)
#14 0.348 - Downloading league/config (v1.2.0)
#14 0.348 - Downloading league/commonmark (2.8.0)
#14 0.348 - Downloading laravel/serializable-closure (v1.3.7)
#14 0.348 - Downloading laravel/framework (v10.50.0)
#14 0.349 - Downloading laravel/prompts (v0.1.25)
#14 0.349 - Downloading guzzlehttp/uri-template (v1.0.5)
#14 0.349 - Downloading fruitcake/php-cors (v1.4.0)
#14 0.349 - Downloading dragonmantank/cron-expression (v3.6.0)
#14 0.349 - Downloading doctrine/inflector (2.1.0)
#14 0.349 - Downloading ageekdev/laravel-barcode (v1.3.0)
#14 0.349 - Downloading anandsiddharth/laravel-paytm-wallet (v2.0.0)
#14 0.349 - Downloading symfony/filesystem (v7.4.0)
#14 0.349 - Downloading psr/http-message (2.0)
#14 0.349 - Downloading mtdowling/jmespath.php (2.8.0)
#14 0.349 - Downloading ralouphie/getallheaders (3.0.3)
#14 0.349 - Downloading psr/http-factory (1.1.0)
#14 0.349 - Downloading guzzlehttp/psr7 (2.8.0)
#14 0.350 - Downloading guzzlehttp/promises (2.3.0)
#14 0.350 - Downloading psr/http-client (1.0.3)
#14 0.350 - Downloading guzzlehttp/guzzle (7.10.0)
#14 0.350 - Downloading aws/aws-crt-php (v1.2.7)
#14 0.350 - Downloading aws/aws-sdk-php (3.369.10)
#14 0.350 - Downloading masterminds/html5 (2.10.0)
#14 0.350 - Downloading thecodingmachine/safe (v3.3.0)
#14 0.350 - Downloading sabberworm/php-css-parser (v9.1.0)
#14 0.350 - Downloading dompdf/php-svg-lib (1.0.2)
#14 0.350 - Downloading dompdf/php-font-lib (1.0.1)
#14 0.350 - Downloading dompdf/dompdf (v3.1.4)
#14 0.350 - Downloading barryvdh/laravel-dompdf (v3.1.1)
#14 0.351 - Downloading dasprid/enum (1.0.7)
#14 0.351 - Downloading dipesh79/laravel-phonepe (1.0.1)
#14 0.351 - Downloading psr/cache (3.0.0)
#14 0.351 - Downloading doctrine/event-manager (2.0.1)
#14 0.351 - Downloading doctrine/persistence (4.1.1)
#14 0.351 - Downloading fakerphp/faker (v1.24.1)
#14 0.351 - Downloading fedapay/fedapay-php (0.4.7)
#14 0.351 - Downloading rize/uri-template (0.4.1)
#14 0.351 - Downloading grpc/grpc (1.74.0)
#14 0.351 - Downloading google/protobuf (v4.33.3)
#14 0.351 - Downloading google/longrunning (0.6.0)
#14 0.351 - Downloading firebase/php-jwt (v6.11.1)
#14 0.352 - Downloading google/auth (v1.50.0)
#14 0.352 - Downloading google/grpc-gcp (v0.4.1)
#14 0.352 - Downloading google/common-protos (4.12.4)
#14 0.352 - Downloading google/gax (v1.40.0)
#14 0.352 - Downloading google/cloud-core (v1.69.0)
#14 0.352 - Downloading hardevine/shoppingcart (3.4)
#14 0.352 - Downloading karim007/laravel-bkash-tokenize (v2.4)
#14 0.352 - Downloading kavenegar/php (v1.2.2)
#14 0.352 - Downloading lcobucci/jwt (5.6.0)
#14 0.352 - Downloading fig/http-message-util (1.1.5)
#14 0.352 - Downloading beste/clock (3.0.0)
#14 0.352 - Downloading kreait/firebase-tokens (5.3.0)
#14 0.353 - Downloading google/cloud-storage (v1.49.0)
#14 0.353 - Downloading cuyz/valinor (2.3.1)
#14 0.353 - Downloading beste/json (1.7.0)
#14 0.353 - Downloading beste/in-memory-cache (1.4.0)
#14 0.353 - Downloading kreait/firebase-php (7.24.0)
#14 0.353 - Downloading laravel/breeze (v1.29.1)
#14 0.353 - Downloading laravel/pint (v1.27.0)
#14 0.353 - Downloading symfony/yaml (v7.4.1)
#14 0.353 - Downloading laravel/sail (v1.52.0)
#14 0.353 - Downloading laravel/sanctum (v3.3.3)
#14 0.353 - Downloading paragonie/random_compat (v9.99.100)
#14 0.353 - Downloading paragonie/constant_time_encoding (v3.1.3)
#14 0.353 - Downloading phpseclib/phpseclib (3.0.48)
#14 0.354 - Downloading league/oauth1-client (v1.11.0)
#14 0.354 - Downloading laravel/socialite (v5.24.1)
#14 0.354 - Downloading nikic/php-parser (v5.7.0)
#14 0.354 - Downloading psy/psysh (v0.12.18)
#14 0.354 - Downloading laravel/tinker (v2.11.0)
#14 0.354 - Downloading php-http/promise (1.3.1)
#14 0.354 - Downloading php-http/httplug (2.4.1)
#14 0.354 - Downloading php-http/guzzle7-adapter (1.1.0)
#14 0.354 - Downloading php-http/message-factory (1.1.0)
#14 0.354 - Downloading clue/stream-filter (v1.7.0)
#14 0.354 - Downloading php-http/message (1.16.2)
#14 0.354 - Downloading moneyphp/money (v4.8.0)
#14 0.355 - Downloading omnipay/common (v3.4.0)
#14 0.355 - Downloading markbaker/matrix (3.0.1)
#14 0.355 - Downloading markbaker/complex (3.0.2)
#14 0.355 - Downloading maennchen/zipstream-php (3.1.2)
#14 0.355 - Downloading ezyang/htmlpurifier (v4.19.0)
#14 0.355 - Downloading composer/pcre (3.3.2)
#14 0.355 - Downloading phpoffice/phpspreadsheet (1.30.0)
#14 0.355 - Downloading composer/semver (3.4.4)
#14 0.355 - Downloading maatwebsite/excel (3.1.67)
#14 0.356 - Downloading mediaburst/clockworksms (2.0.0)
#14 0.356 - Downloading melipayamak/php (1.0.0)
#14 0.356 - Downloading doctrine/common (3.5.0)
#14 0.356 - Downloading doctrine/annotations (1.14.4)
#14 0.356 - Downloading mercadopago/dx-php (2.6.2)
#14 0.356 - Downloading hamcrest/hamcrest-php (v2.1.1)
#14 0.356 - Downloading mockery/mockery (1.6.12)
#14 0.356 - Downloading composer/ca-bundle (1.5.10)
#14 0.356 - Downloading mollie/mollie-api-php (v2.79.1)
#14 0.356 - Downloading setasign/fpdi (v2.6.4)
#14 0.356 - Downloading myclabs/deep-copy (1.13.4)
#14 0.357 - Downloading mpdf/psr-log-aware-trait (v3.0.0)
#14 0.357 - Downloading mpdf/psr-http-message-shim (v2.0.1)
#14 0.357 - Downloading mpdf/mpdf (v8.2.7)
#14 0.357 - Downloading filp/whoops (2.18.4)
#14 0.357 - Downloading nunomaduro/collision (v7.12.0)
#14 0.357 - Downloading nwidart/laravel-modules (10.0.6)
#14 0.357 - Downloading omnipay/paypal (v3.0.2)
#14 0.357 - Downloading omnipay/stripe (v3.2.0)
#14 0.357 - Downloading paytm/paytmchecksum (v1.1.0)
#14 0.357 - Downloading sebastian/version (4.0.1)
#14 0.357 - Downloading sebastian/type (4.0.0)
#14 0.357 - Downloading sebastian/recursion-context (5.0.1)
#14 0.358 - Downloading sebastian/object-reflector (3.0.0)
#14 0.358 - Downloading sebastian/object-enumerator (5.0.0)
#14 0.358 - Downloading sebastian/global-state (6.0.2)
#14 0.358 - Downloading sebastian/exporter (5.1.4)
#14 0.358 - Downloading sebastian/environment (6.1.0)
#14 0.358 - Downloading sebastian/diff (5.1.1)
#14 0.358 - Downloading sebastian/comparator (5.0.4)
#14 0.358 - Downloading sebastian/code-unit (2.0.0)
#14 0.358 - Downloading sebastian/cli-parser (2.0.1)
#14 0.358 - Downloading phpunit/php-timer (6.0.0)
#14 0.358 - Downloading phpunit/php-text-template (3.0.1)
#14 0.358 - Downloading phpunit/php-invoker (4.0.0)
#14 0.358 - Downloading phpunit/php-file-iterator (4.1.0)
#14 0.358 - Downloading theseer/tokenizer (1.3.1)
#14 0.359 - Downloading sebastian/lines-of-code (2.0.2)
#14 0.359 - Downloading sebastian/complexity (3.2.0)
#14 0.359 - Downloading sebastian/code-unit-reverse-lookup (3.0.0)
#14 0.359 - Downloading phpunit/php-code-coverage (10.1.16)
#14 0.359 - Downloading phar-io/version (3.2.1)
#14 0.359 - Downloading phar-io/manifest (2.0.4)
#14 0.359 - Downloading phpunit/phpunit (10.5.60)
#14 0.359 - Downloading rmccue/requests (v2.0.17)
#14 0.359 - Downloading razorpay/razorpay (2.9.2)
#14 0.359 - Downloading safiull/laravel-installer (3.5.2)
#14 0.359 - Downloading bacon/bacon-qr-code (2.0.8)
#14 0.359 - Downloading simplesoftwareio/simple-qrcode (4.2.0)
#14 0.360 - Downloading smsgatewayme/client (v0.0.1)
#14 0.360 - Downloading spatie/error-solutions (1.1.3)
#14 0.360 - Downloading spatie/backtrace (1.8.1)
#14 0.360 - Downloading spatie/flare-client-php (1.10.1)
#14 0.360 - Downloading spatie/ignition (1.15.1)
#14 0.360 - Downloading spatie/laravel-ignition (2.9.1)
#14 0.360 - Downloading spatie/laravel-permission (6.24.0)
#14 0.360 - Downloading stripe/stripe-php (v13.18.0)
#14 0.360 - Downloading twilio/sdk (8.10.1)
#14 0.360 - Downloading tzsk/sms (9.0.0)
#14 10.38 A connection timeout was encountered. If you intend to run Composer without connecting to the internet, run the command again prefixed with COMPOSER_DISABLE_NETWORK=1 to make Composer run in offline mode.

View File

@@ -53,7 +53,8 @@
"mockery/mockery": "^1.4.4",
"nunomaduro/collision": "^7.0",
"phpunit/phpunit": "^10.1",
"spatie/laravel-ignition": "^2.0"
"spatie/laravel-ignition": "^2.0",
"knuckleswtf/scribe": "5.9.0"
},
"autoload": {
"psr-4": {

92
dockerfile.txt Normal file
View File

@@ -0,0 +1,92 @@
FROM php:8.2-fpm-alpine
# Install system dependencies
RUN apk add --no-cache \
vim \
xxd \
libzip-dev \
postgresql-dev \
libpng-dev \
libjpeg-turbo-dev \
freetype-dev \
oniguruma-dev \
git \
curl \
nodejs \
npm \
&& docker-php-ext-configure gd --with-freetype --with-jpeg \
&& docker-php-ext-install -j$(nproc) \
pdo_pgsql \
pgsql \
opcache \
zip \
bcmath \
gd \
exif \
pcntl
# Configure git to allow safe directory
RUN git config --global --add safe.directory /var/www/html
# Install Composer
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
WORKDIR /var/www/html
# Copy ONLY composer files first
COPY composer.json ./
# Create storage and bootstrap directories needed for composer scripts
RUN mkdir -p storage/framework/cache \
&& mkdir -p storage/framework/sessions \
&& mkdir -p storage/framework/views \
&& mkdir -p storage/framework/testing \
&& mkdir -p storage/logs \
&& mkdir -p bootstrap/cache \
&& chmod -R 777 storage \
&& chmod -R 777 bootstrap/cache
# Configure Composer for stability on unstable networks
ENV COMPOSER_ALLOW_SUPERUSER=1 \
COMPOSER_MEMORY_LIMIT=-1 \
COMPOSER_PROCESS_TIMEOUT=2000
# Install PHP dependencies (this layer will be cached)
RUN composer config -g repo.packagist composer https://packagist.org \
&& composer install --no-interaction --no-progress --no-scripts --prefer-dist
# Isolate the clean vendor directory
#RUN mv vendor /vendor_clean
# Copy the rest of the application files (will ignore vendor because of .dockerignore)
COPY --chown=www-data:www-data . .
# Restore the clean vendor directory and set proper permissions
#RUN rm -rf vendor && mv /vendor_clean vendor && chown -R www-data:www-data vendor
# Final build verification
RUN if [ ! -f vendor/laravel/prompts/src/helpers.php ]; then \
echo "ERROR: laravel/prompts/src/helpers.php missing!" && exit 1; \
fi
# Final check and debug listing
RUN ls -l vendor/laravel/prompts/src/helpers.php \
&& du -sh vendor/laravel/prompts
# Copy package files for NPM
COPY package*.json ./
COPY vite.config.js ./
# Install NPM dependencies
RUN npm install --legacy-peer-deps --no-audit
# Build Vite assets
RUN npm run build
# Copy entrypoint script and ensure it's executable
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
# Let the entrypoint handle artisan commands at runtime
EXPOSE 8000 9000
ENTRYPOINT ["/entrypoint.sh"]

60
dockerfile2.txt Normal file
View File

@@ -0,0 +1,60 @@
FROM php:8.4-fpm
# 1. Setup NodeSource untuk instalasi Node.js v20 (Cepat & Stabil)
RUN apt-get update && apt-get install -y ca-certificates curl gnupg \
&& curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg \
&& echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_20.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list
# 2. Install dependencies utama dengan apt-get
RUN apt-get update && apt-get install -y \
git \
libpng-dev \
libonig-dev \
libxml2-dev \
libpq-dev \
libfreetype6-dev \
libjpeg62-turbo-dev \
libzip-dev \
zip \
unzip \
nodejs \
&& apt-get clean && rm -rf /var/lib/apt/lists/*
# 3. Install PHP Extensions (TERMASUK pgsql / pdo_pgsql)
RUN docker-php-ext-configure gd --with-freetype --with-jpeg \
&& docker-php-ext-install pdo_mysql pdo_pgsql pgsql mbstring exif pcntl bcmath gd zip
# 4. Get latest Composer
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
WORKDIR /var/www
# 5. Copy existing application directory contents
COPY . /var/www
# 5.5 PASTIKAN FOLDER CACHE & STORAGE TERBUAT SEBELUM COMPOSER RUN
RUN mkdir -p /var/www/storage/framework/cache/data \
/var/www/storage/framework/sessions \
/var/www/storage/framework/views \
/var/www/storage/framework/testing \
/var/www/storage/logs \
/var/www/bootstrap/cache
# 6. Install dependencies
ENV COMPOSER_ALLOW_SUPERUSER=1 \
COMPOSER_MEMORY_LIMIT=-1
RUN composer install --no-interaction --optimize-autoloader
# 7. Install & Build Frontend
RUN npm install
RUN npm run build
# 8. Set permissions
RUN chown -R www-data:www-data /var/www/storage /var/www/bootstrap/cache \
&& chmod -R 775 /var/www/storage /var/www/bootstrap/cache
# 9. Expose port 8000 and start php artisan serve
EXPOSE 8000
# artisan serve berjalan langsung
CMD ["php", "artisan", "serve", "--host=0.0.0.0", "--port=8000"]

View File

@@ -9,6 +9,15 @@ echo "========================================"
echo "Current user: $(whoami)"
echo "Current directory: $(pwd)"
# Set debug based on environment
if [ "$APP_ENV" = "production" ]; then
export APP_DEBUG=false
export LOG_LEVEL=error
else
export APP_DEBUG=true
export LOG_LEVEL=debug
fi
# Create ALL required Laravel directories
echo "Creating storage directories..."
mkdir -p storage/framework/cache
@@ -54,33 +63,138 @@ EOF
fi
fi
# Generate key if not present
if ! grep -q "APP_KEY=" .env || grep -q "APP_KEY=$" .env; then
echo "Generating application key..."
php artisan key:generate --no-interaction --force || echo "Key generation failed"
# Generate key if not present (only if APP_KEY is empty or missing)
echo "Checking .env for APP_KEY..."
if ! grep -E "APP_KEY=.{10,}" .env; then
echo "APP_KEY is empty or missing. Generating..."
# If the file doesn't have APP_KEY= at all, add it so artisan can replace it
if ! grep -q "APP_KEY=" .env; then
echo "Appending APP_KEY= placeholder..."
echo "" >> .env
echo "APP_KEY=" >> .env
fi
php artisan key:generate --no-interaction --force || echo "❌ Key generation failed!"
fi
# Clear all caches to ensure fresh state
echo "Clearing caches..."
php artisan config:clear || true
php artisan cache:clear || true
php artisan view:clear || true
php artisan route:clear || true
echo "Verifying APP_KEY after generation:"
grep "APP_KEY" .env
# Optional: Run migrations if needed
# php artisan migrate --force
# Ensure vendor directory matches composer.lock exactly, syncing missing packages
echo "Verifying and synchronizing Composer dependencies..."
# Check if scribe is in composer.json but not installed
if grep -q "knuckleswtf/scribe" composer.json; then
if ! composer show --locked knuckleswtf/scribe --no-ansi 2>/dev/null | grep -q "name"; then
echo "📦 knuckleswtf/scribe found in composer.json but not in lock file. Installing..."
composer require --dev knuckleswtf/scribe --no-interaction --no-progress --update-with-dependencies
else
echo "✅ knuckleswtf/scribe is already in lock file"
composer install --no-interaction --optimize-autoloader
fi
else
# Normal install
composer install --no-interaction --optimize-autoloader
fi
# If install fails, try update
if [ $? -ne 0 ]; then
echo "⚠️ Composer install failed, attempting update..."
composer update --no-interaction --optimize-autoloader --with-all-dependencies
fi
# Install and publish Scribe assets
echo "Setting up Scribe/OpenAPI documentation..."
# Publish Scribe config (if not already published)
if [ ! -f config/scribe.php ]; then
echo "Publishing Scribe configuration..."
php artisan vendor:publish --tag=scribe-config --force
fi
# Generate OpenAPI/Swagger documentation
if [ "$APP_ENV" != "production" ] || [ "$AUTO_GENERATE_API_DOCS" = "true" ]; then
echo "Generating API documentation..."
# Generate Scribe documentation
php artisan scribe:generate --no-interaction || echo "⚠️ Scribe generation failed, check your API routes"
# For OpenAPI/Swagger UI specifically
if [ -f public/docs/openapi.yaml ] || [ -f public/docs/collection.json ]; then
echo "✅ Documentation generated successfully"
echo "📚 Swagger UI available at: /docs (if configured)"
echo "📄 OpenAPI spec available at: /docs/openapi.yaml"
else
echo "⚠️ Documentation files not found after generation"
fi
fi
# Create symbolic link for Scribe assets if needed
if [ -d vendor/knuckleswtf/scribe/dist ]; then
echo "Creating Scribe assets symlink..."
mkdir -p public/vendor/scribe
cp -r vendor/knuckleswtf/scribe/dist/* public/vendor/scribe/ 2>/dev/null || \
ln -sf ../vendor/knuckleswtf/scribe/dist public/vendor/scribe 2>/dev/null || true
fi
# FIX: Ensure all generated/downloaded files are fully readable and executable
echo "Setting correct ownership for web server..."
chown -R www-data:www-data vendor
chmod -R 755 vendor
# Verify vendor integrity at PHP level
echo "Checking vendor visibility and READABILITY for PHP..."
ls -la /var/www/html/vendor/laravel/prompts/src/helpers.php
php -r 'if (file_exists("/var/www/html/vendor/laravel/prompts/src/helpers.php")) { echo "✅ file_exists true\n"; }'
php -r 'require "/var/www/html/vendor/laravel/prompts/src/helpers.php"; echo "✅ PHP successfully REQUIRED helpers.php\n";'
# Run production optimizations for production environment
if [ "$APP_ENV" = "production" ] || [ "$MODE" = "production" ]; then
echo "Running production optimizations..."
php artisan config:cache
php artisan view:cache
php artisan event:cache
else
# Clear all caches to ensure fresh state for non-production
echo "Clearing caches..."
php artisan config:clear || true
php artisan view:clear || true
php artisan route:clear || true
php artisan cache:clear || true
php artisan optimize:clear || true
fi
# Verify Vite manifest exists
if [ -f public/build/manifest.json ]; then
echo "✅ Vite manifest found at public/build/manifest.json"
else
echo "⚠️ Warning: Vite manifest not found! Assets may not load correctly."
echo "Current contents of public/build:"
ls -la public/build/ 2>/dev/null || echo "public/build directory does not exist"
fi
# Optional: Run migrations if needed (with safety for production)
if [ "$AUTO_MIGRATE" = "true" ] || [ "$APP_ENV" != "production" ]; then
echo "Running migrations..."
php artisan migrate --force
fi
echo "========================================"
echo "Setup complete. Starting application..."
echo "========================================"
# Start the application with proper error handling
if [ "$MODE" = "staging" ]; then
echo "Starting PHP built-in server on port 8000..."
# Use exec with error capture
exec php artisan serve --host=0.0.0.0 --port=8000 2>&1 | tee -a /var/log/php-server.log
# Start the application based on environment
if [ "$APP_ENV" = "production" ] || [ "$MODE" = "production" ]; then
echo "Starting PHP built-in server on port 8000 (Production mode)..."
echo "Document root: /var/www/html/public"
echo "Workers: Single-threaded PHP server (use Nginx for production scaling)"
# Use direct php -S for production (optimized)
exec php -d opcache.enable=1 -d opcache.memory_consumption=128 -d opcache.max_accelerated_files=10000 -S 0.0.0.0:8000 -t public
elif [ "$MODE" = "staging" ]; then
echo "Starting PHP built-in server on port 8000 (Staging mode)..."
# Use direct php -S to eliminate any artisan wrapper issues
exec php -d opcache.enable=0 -S 0.0.0.0:8000 -t public
else
echo "Starting PHP-FPM on port 9000..."
echo "Starting PHP-FPM on port 9000 (Development mode)..."
# Ensure PHP-FPM is properly configured
exec php-fpm -F
fi

52
entrypoint2.sh Normal file
View File

@@ -0,0 +1,52 @@
#!/bin/sh
set -e
echo "========================================"
echo "Starting KulakPOS Container Setup..."
echo "========================================"
# Tampilkan info dasar untuk debugging
echo "Current user: $(whoami)"
echo "Current directory: $(pwd)"
# 1. Fallback untuk kelengkapan env (agar container minimal bisa booting)
if [ ! -f .env ]; then
echo "⚠️ .env tidak ditemukan!"
if [ -f .env.example ]; then
echo "Menyalin dari .env.example..."
cp .env.example .env
fi
fi
# 2. Pastikan permission path krusial aman (berguna jika terkena Volume Mount di Docker Compose)
echo "Menyesuaikan izin (permissions) untuk storage & bootstrap/cache..."
mkdir -p storage/framework/cache/data storage/framework/sessions storage/framework/views storage/framework/testing storage/logs bootstrap/cache
chown -R www-data:www-data storage bootstrap/cache
chmod -R 775 storage bootstrap/cache
# 3. Bersihkan cache instalasi/config lama
echo "Membersihkan cache Laravel..."
php artisan config:clear || true
# 4. Verifikasi Vite Build
if [ -f public/build/manifest.json ]; then
echo "✅ Vite manifest ditemukan."
else
echo "⚠️ Peringatan: Vite manifest public/build/manifest.json tidak ditemukan."
fi
# ========================================
# PENTING: Dihilangkan dari script ini:
# - composer install (Sudah dijamin ada pada Dockerfile, bukan di runtime)
# - chmod 777 (Diganti menjadi 775 dan chown www-data)
# - php -S (Kita standarkan aplikasi menggunakan php-fpm murni dari CMD Dockerfile)
# - Paksaan export APP_DEBUG
# ========================================
echo "========================================"
echo "Selesai. Menjalankan proses utama ($@)..."
echo "========================================"
# Serahkan eksekusi ke CMD milik Dockerfile (yaitu php-fpm)
exec "$@"

2050
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -6,19 +6,19 @@
},
"devDependencies": {
"@tailwindcss/forms": "^0.5.2",
"@vitejs/plugin-react": "^6.0.1",
"@vitejs/plugin-react": "^4.3.0",
"alpinejs": "^3.4.2",
"autoprefixer": "^10.4.2",
"axios": "^1.1.2",
"laravel-vite-plugin": "^2.0.0",
"laravel-vite-plugin": "^1.2.0",
"lodash": "^4.17.19",
"postcss": "^8.4.6",
"tailwindcss": "^3.1.0",
"vite": "^8.0.3"
"vite": "^6.0.0"
},
"dependencies": {
"lucide-react": "^1.7.0",
"react": "^19.2.4",
"react-dom": "^19.2.4"
"react": "^18.2.0",
"react-dom": "^18.2.0"
}
}
}

12
public/images/gopay.svg Normal file
View File

@@ -0,0 +1,12 @@
<svg xmlns="http://www.w3.org/2000/svg" width="63" height="16" viewBox="0 0 63 16">
<g fill="none" fill-rule="evenodd">
<path fill="#FFF" fill-opacity=".01" d="M0 0h63v16H0z"/>
<g transform="translate(0 1.143)">
<ellipse cx="6.811" cy="6.857" fill="#00AED6" fill-rule="nonzero" rx="6.811" ry="6.857"/>
<path fill="#FFF" d="M10.778 6.644a1.587 1.587 0 0 0-1.652-1.5H4.824a.285.285 0 0 1-.284-.286c0-.158.127-.286.284-.286h4.359a1.362 1.362 0 0 0-.993-1.26 10.97 10.97 0 0 0-3.84 0 1.82 1.82 0 0 0-1.362 1.526 13.711 13.711 0 0 0 0 4.06 1.92 1.92 0 0 0 1.552 1.526 19.13 19.13 0 0 0 4.748 0 1.669 1.669 0 0 0 1.317-1.44c.14-.772.199-1.556.173-2.34zm-1.413.96v.254a.285.285 0 0 1-.284.286.285.285 0 0 1-.284-.286v-.254a.427.427 0 0 1 .284-.746.427.427 0 0 1 .284.746z"/>
</g>
<g fill="#000" fill-rule="nonzero">
<path d="M18.937 11.414a2.921 2.921 0 0 0 2.545 1.252c1.187 0 2.059-.763 2.059-1.8v-.547h-.029c-.65.64-1.537.974-2.444.922a3.955 3.955 0 0 1-3.513-1.94 4.012 4.012 0 0 1-.037-4.033 3.956 3.956 0 0 1 3.478-2.002 3.39 3.39 0 0 1 2.516.892h.029V3.41h2.03v7.428c0 2.159-1.7 3.656-4.089 3.656a4.87 4.87 0 0 1-4.06-1.814l1.515-1.266zm4.519-4.622c0-.863-.973-1.655-2.059-1.655-1.373 0-2.288.835-2.288 2.087-.04.594.18 1.175.605 1.588a1.995 1.995 0 0 0 1.597.557c1.187 0 2.145-.748 2.145-1.684v-.893zM30.916 3.194c2.474 0 4.276 1.77 4.276 4.03 0 2.26-1.802 4.031-4.276 4.031a4.005 4.005 0 0 1-3.692-1.935 4.063 4.063 0 0 1 0-4.191 4.005 4.005 0 0 1 3.692-1.935zm0 1.87a2.152 2.152 0 0 0-2.13 2.17 2.152 2.152 0 0 0 2.15 2.15 2.152 2.152 0 0 0 2.14-2.16 2.075 2.075 0 0 0-.605-1.562 2.045 2.045 0 0 0-1.555-.597zM36.29 3.41h2.03v.676h.03a3.359 3.359 0 0 1 2.444-.892c2.18.04 3.928 1.828 3.932 4.023.004 2.196-1.738 3.99-3.918 4.038-.86.02-1.7-.265-2.373-.806h-.029v3.829H36.29V3.41zm4.176 1.67c-1.116 0-2.06.791-2.06 1.655v.964c0 .922.916 1.684 2.073 1.684a2.145 2.145 0 0 0 2.131-2.158 2.145 2.145 0 0 0-2.144-2.146zM48.803 6.49c1.387-.187 1.802-.388 1.802-.777 0-.504-.53-.806-1.344-.806a1.79 1.79 0 0 0-1.888 1.367l-2.002-.417c.286-1.555 1.874-2.663 3.832-2.663 2.216 0 3.59 1.137 3.59 2.993v4.852H50.89v-.835h-.03a3.117 3.117 0 0 1-2.559 1.051c-1.673 0-2.83-.921-2.83-2.275 0-1.425.943-2.159 3.331-2.49zm1.973.806h-.028c-.187.274-.587.432-1.616.62-1.244.23-1.687.474-1.687.92 0 .461.372.663 1.172.663 1.216 0 2.16-.562 2.16-1.296v-.907zM56.82 10.622L53.317 3.41h2.331l2.302 4.98h.028l2.274-4.98h2.345L57.35 14.278h-2.331z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 290 KiB

BIN
public/images/ovo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

BIN
public/images/shopeepay.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

View File

@@ -6,13 +6,16 @@ import {
MonitorSmartphone, Printer, ShieldCheck, Wrench, Sparkles,
Laptop, Tablet, ArrowRight, Bot, PieChart, TrendingUp, BrainCircuit,
Package, FileText, ShoppingCart, Warehouse, Users, GitMerge, ArrowLeft,
Utensils, Zap, Clock, UserCheck, CalendarDays, History
Utensils, Zap, Clock, UserCheck, CalendarDays, History, Lock, Wallet
} from 'lucide-react';
export default function App() {
const [isMenuOpen, setIsMenuOpen] = useState(false);
const [activeIndustryTab, setActiveIndustryTab] = useState('fnb');
const [currentPage, setCurrentPage] = useState('home'); // 'home' or 'industry-detail'
const [currentPage, setCurrentPage] = useState('home'); // 'home', 'industry-detail', or 'checkout'
const [selectedPlan, setSelectedPlan] = useState(null);
const [paymentMethod, setPaymentMethod] = useState('credit_card');
const [selectedEWallet, setSelectedEWallet] = useState('gopay');
// Scroll to top when changing page
useEffect(() => {
@@ -80,34 +83,40 @@ export default function App() {
const activeIndustry = industries.find(i => i.id === activeIndustryTab);
// Generate Date for Next Month
const getNextMonthDate = () => {
const nextMonth = new Date();
nextMonth.setMonth(nextMonth.getMonth() + 1);
return nextMonth.toLocaleDateString('id-ID', { day: 'numeric', month: 'long', year: 'numeric' });
};
const renderNavbar = () => (
<nav className="fixed top-0 w-full bg-white/95 backdrop-blur-md z-50 border-b border-slate-200 transition-all shadow-sm">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex justify-between items-center h-20">
<div className="flex-shrink-0 flex items-center gap-2 cursor-pointer" onClick={() => setCurrentPage('home')}>
<div className="w-10 h-10 bg-teal-600 rounded-lg flex items-center justify-center shadow-md">
<Store className="text-white w-6 h-6" />
</div>
<span className="font-extrabold text-2xl text-slate-900 tracking-tight hidden sm:block">
Kulak<span className="text-teal-600">pos</span>
</span>
<div className="flex-shrink-0 flex items-center gap-2 cursor-pointer" onClick={() => { setCurrentPage('home'); window.location.href = '/'; }}>
<img src="/images/logokulakpos.png" alt="Kulakpos" className="h-10 w-auto" />
</div>
<div className="hidden md:flex space-x-8 items-center">
<a href="#fitur" onClick={(e) => { e.preventDefault(); setCurrentPage('home'); setTimeout(() => document.getElementById('fitur')?.scrollIntoView({behavior: 'smooth'}), 100); }} className="text-slate-600 hover:text-teal-600 font-semibold transition-colors text-sm">Fitur POS</a>
<a href="#ai" onClick={(e) => { e.preventDefault(); setCurrentPage('home'); setTimeout(() => document.getElementById('ai')?.scrollIntoView({behavior: 'smooth'}), 100); }} className="text-teal-600 hover:text-teal-700 font-bold transition-colors text-sm flex items-center gap-1.5 bg-teal-50 px-3 py-1.5 rounded-full border border-teal-100">
<a href="/#fitur" onClick={(e) => { e.preventDefault(); setCurrentPage('home'); setTimeout(() => document.getElementById('fitur')?.scrollIntoView({behavior: 'smooth'}), 100); }} className="text-slate-600 hover:text-teal-600 font-semibold transition-colors text-sm">Fitur POS</a>
<a href="/#ai" onClick={(e) => { e.preventDefault(); setCurrentPage('home'); setTimeout(() => document.getElementById('ai')?.scrollIntoView({behavior: 'smooth'}), 100); }} className="text-teal-600 hover:text-teal-700 font-bold transition-colors text-sm flex items-center gap-1.5 bg-teal-50 px-3 py-1.5 rounded-full border border-teal-100">
<Bot className="w-4 h-4" /> AI Assistance
</a>
<a href="#perangkat" onClick={(e) => { e.preventDefault(); setCurrentPage('home'); setTimeout(() => document.getElementById('perangkat')?.scrollIntoView({behavior: 'smooth'}), 100); }} className="text-slate-600 hover:text-teal-600 font-semibold transition-colors text-sm">Perangkat</a>
<a href="#solusi" onClick={(e) => { e.preventDefault(); setCurrentPage('home'); setTimeout(() => document.getElementById('solusi')?.scrollIntoView({behavior: 'smooth'}), 100); }} className="text-slate-600 hover:text-teal-600 font-semibold transition-colors text-sm">Solusi Industri</a>
<a href="#harga" onClick={(e) => { e.preventDefault(); setCurrentPage('home'); setTimeout(() => document.getElementById('harga')?.scrollIntoView({behavior: 'smooth'}), 100); }} className="text-slate-600 hover:text-teal-600 font-semibold transition-colors text-sm">Harga</a>
<a href="/#perangkat" onClick={(e) => { e.preventDefault(); setCurrentPage('home'); setTimeout(() => document.getElementById('perangkat')?.scrollIntoView({behavior: 'smooth'}), 100); }} className="text-slate-600 hover:text-teal-600 font-semibold transition-colors text-sm">Perangkat</a>
<a href="/#solusi" onClick={(e) => { e.preventDefault(); setCurrentPage('home'); setTimeout(() => document.getElementById('solusi')?.scrollIntoView({behavior: 'smooth'}), 100); }} className="text-slate-600 hover:text-teal-600 font-semibold transition-colors text-sm">Solusi Industri</a>
<a href="/#harga" onClick={(e) => { e.preventDefault(); setCurrentPage('home'); setTimeout(() => document.getElementById('harga')?.scrollIntoView({behavior: 'smooth'}), 100); }} className="text-slate-600 hover:text-teal-600 font-semibold transition-colors text-sm">Harga</a>
</div>
<div className="hidden md:flex items-center space-x-4">
<a href="/login" className="text-slate-600 font-semibold hover:text-red-600 transition-colors text-sm">Masuk</a>
<a href="/plans" className="bg-teal-600 hover:bg-teal-700 text-white px-5 py-2.5 rounded-lg font-semibold transition-all shadow-md text-sm flex items-center gap-2">
<button
type="button"
onClick={() => { setSelectedPlan({ name: 'Pro Business', price: 100000 }); setCurrentPage('checkout'); window.scrollTo(0, 0); }}
className="bg-teal-600 hover:bg-teal-700 text-white px-5 py-2.5 rounded-lg font-semibold transition-all shadow-md text-sm flex items-center gap-2"
>
Coba Gratis <ArrowRight className="w-4 h-4" />
</a>
</button>
</div>
<div className="md:hidden flex items-center">
@@ -117,6 +126,7 @@ export default function App() {
</div>
</div>
</div>
</nav>
);
@@ -125,13 +135,8 @@ export default function App() {
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-5 gap-12 lg:gap-8 mb-12">
<div className="lg:col-span-2">
<div className="flex items-center gap-2 mb-6 cursor-pointer" onClick={() => setCurrentPage('home')}>
<div className="w-10 h-10 bg-teal-600 rounded-lg flex items-center justify-center shadow-md">
<Store className="text-white w-6 h-6" />
</div>
<span className="font-extrabold text-2xl text-slate-900 tracking-tight">
Kulak<span className="text-teal-600">pos</span>
</span>
<div className="flex items-center gap-2 mb-6 cursor-pointer" onClick={() => { setCurrentPage('home'); window.location.href = '/'; }}>
<img src="/images/logokulakpos.png" alt="Kulakpos" className="h-10 w-auto" />
</div>
<p className="text-sm mb-6 max-w-sm leading-relaxed text-slate-500 font-medium">
Penyedia layanan perangkat lunak kasir (SaaS POS) terkemuka di Indonesia. Misi kami adalah memberdayakan UMKM untuk naik kelas melalui teknologi kasir yang cerdas dan mudah.
@@ -176,7 +181,7 @@ export default function App() {
const renderHomePage = () => (
<>
{/* HERO SECTION */}
<section className="pt-32 pb-20 lg:pt-40 lg:pb-24 px-4 sm:px-6 lg:px-8 max-w-7xl mx-auto relative overflow-hidden">
<section className="pt-28 pb-16 lg:pt-40 lg:pb-24 px-4 sm:px-6 lg:px-8 max-w-7xl mx-auto relative overflow-hidden">
<div className="absolute top-0 right-0 -translate-y-12 translate-x-1/3 w-96 h-96 bg-teal-50 rounded-full blur-3xl opacity-50 z-0"></div>
<div className="absolute top-40 left-0 -translate-x-1/2 w-64 h-64 bg-red-50 rounded-full blur-3xl opacity-50 z-0"></div>
@@ -185,7 +190,7 @@ export default function App() {
<Sparkles className="w-4 h-4" />
Mitra Digital #1 untuk UMKM Naik Kelas
</div>
<h1 className="text-4xl sm:text-5xl lg:text-6xl font-extrabold text-slate-900 leading-[1.15] mb-6 tracking-tight">
<h1 className="text-3xl sm:text-5xl lg:text-6xl font-extrabold text-slate-900 leading-tight md:leading-[1.15] mb-6 tracking-tight">
Sistem Kasir Pintar dengan <br className="hidden md:block" />
Teknologi <span className="text-teal-600">AI Assistance</span>
</h1>
@@ -193,12 +198,16 @@ export default function App() {
Tinggalkan cara manual. Kendalikan penjualan, pantau stok bahan baku, hingga analisis laba rugi secara real-time dari satu dashboard profesional Kulakpos.id.
</p>
<div className="flex flex-col sm:flex-row items-center gap-4 justify-center">
<a href="/plans" className="w-full sm:w-auto bg-teal-600 hover:bg-teal-700 text-white px-8 py-4 rounded-lg text-base font-bold transition-all shadow-lg shadow-teal-600/20 flex items-center justify-center gap-2 group decoration-transparent">
<button
type="button"
onClick={() => { setSelectedPlan({ name: 'Pro Business', price: 100000 }); setCurrentPage('checkout'); window.scrollTo(0, 0); }}
className="w-full sm:w-auto bg-teal-600 hover:bg-teal-700 text-white px-8 py-4 rounded-lg text-base font-bold transition-all shadow-lg shadow-teal-600/20 flex items-center justify-center gap-2 group decoration-transparent"
>
Coba Gratis Sekarang <ChevronRight className="w-5 h-5 group-hover:translate-x-1 transition-transform" />
</a>
<button className="w-full sm:w-auto bg-white border-2 border-slate-200 hover:border-red-600 hover:text-red-600 text-slate-700 px-8 py-4 rounded-lg text-base font-bold transition-all">
Jadwalkan Demo
</button>
<a href="/contact-us" className="w-full sm:w-auto bg-white border-2 border-slate-200 hover:border-red-600 hover:text-red-600 text-slate-700 px-8 py-4 rounded-lg text-base font-bold transition-all text-center decoration-transparent">
Jadwalkan Demo
</a>
</div>
</div>
@@ -460,7 +469,13 @@ export default function App() {
))}
<li className="flex gap-2 text-sm font-medium italic opacity-50"><X className="w-5 h-5" /> Multi-branch Allowed</li>
</ul>
<button className="w-full py-3 rounded-xl border-2 font-bold hover:border-teal-600 transition-colors">Pilih Basic</button>
<button
type="button"
onClick={() => { setSelectedPlan({ name: 'Basic', price: 50000 }); setCurrentPage('checkout'); window.scrollTo(0, 0); }}
className="w-full py-3 rounded-xl border-2 font-bold hover:border-teal-600 text-slate-700 hover:text-teal-600 transition-colors"
>
Pilih Basic
</button>
</div>
{/* Pro */}
<div className="bg-teal-900 rounded-3xl p-8 border border-teal-800 flex flex-col transform md:-translate-y-4 shadow-2xl text-white">
@@ -473,7 +488,13 @@ export default function App() {
<li key={f} className="flex gap-2 text-sm font-medium"><CheckCircle className="w-5 h-5 text-teal-400" />{f}</li>
))}
</ul>
<button className="w-full py-3 rounded-xl bg-teal-500 text-teal-950 font-black hover:bg-teal-400 transition-all">Pilih Pro</button>
<button
type="button"
onClick={() => { setSelectedPlan({ name: 'Pro Business', price: 100000 }); setCurrentPage('checkout'); window.scrollTo(0, 0); }}
className="w-full py-3 rounded-xl bg-teal-500 text-teal-950 font-black hover:bg-teal-400 transition-all"
>
Pilih Pro
</button>
</div>
{/* Enterprise */}
<div className="bg-white rounded-3xl p-8 border border-slate-200 flex flex-col shadow-sm">
@@ -580,19 +601,222 @@ export default function App() {
</div>
);
const renderCheckoutPage = () => {
// Default fallback if directly opened without selecting
const plan = selectedPlan || { name: 'Pro Business', price: 100000 };
return (
<div className="min-h-screen bg-slate-50 pt-28 pb-20">
<div className="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8">
<button
onClick={() => setCurrentPage('home')}
className="flex items-center gap-2 text-slate-500 hover:text-teal-600 font-bold mb-8 transition-colors"
>
<ArrowLeft className="w-5 h-5" /> Kembali
</button>
<div className="grid lg:grid-cols-3 gap-8">
{/* Left Column: Form & Payment Methods */}
<div className="lg:col-span-2 space-y-8">
<div className="bg-white p-8 rounded-3xl border border-slate-200 shadow-sm">
<h2 className="text-2xl font-extrabold text-slate-900 mb-6">Detail Pendaftaran</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<label className="block text-sm font-bold text-slate-700 mb-2">Nama Lengkap</label>
<input type="text" className="w-full px-4 py-3 rounded-xl border border-slate-200 focus:border-teal-500 focus:ring-2 focus:ring-teal-200 outline-none transition-all" placeholder="John Doe" />
</div>
<div>
<label className="block text-sm font-bold text-slate-700 mb-2">Email Perusahaan</label>
<input type="email" className="w-full px-4 py-3 rounded-xl border border-slate-200 focus:border-teal-500 focus:ring-2 focus:ring-teal-200 outline-none transition-all" placeholder="john@bisnis.com" />
</div>
<div className="md:col-span-2">
<label className="block text-sm font-bold text-slate-700 mb-2">Nama Toko / Bisnis</label>
<input type="text" className="w-full px-4 py-3 rounded-xl border border-slate-200 focus:border-teal-500 focus:ring-2 focus:ring-teal-200 outline-none transition-all" placeholder="Kedai Kopi Nusantara" />
</div>
</div>
</div>
<div className="bg-white p-8 rounded-3xl border border-slate-200 shadow-sm">
<div className="flex flex-col sm:flex-row justify-between sm:items-center gap-4 mb-6">
<div>
<h2 className="text-2xl font-extrabold text-slate-900">Metode Pembayaran</h2>
<p className="text-sm text-slate-500 font-medium mt-1">Untuk penagihan bulan ke-2</p>
</div>
<div className="flex items-center gap-2 bg-slate-50 px-3 py-1.5 rounded-lg border border-slate-100 w-max">
<span className="text-[10px] font-bold text-slate-400 uppercase tracking-widest">Powered by</span>
<span className="font-black text-[#013550] text-lg tracking-tighter">xendit</span>
</div>
</div>
<div className="grid grid-cols-2 gap-4 mb-8">
<div
onClick={() => setPaymentMethod('credit_card')}
className={`p-4 rounded-xl border-2 cursor-pointer flex flex-col items-center gap-3 transition-all ${paymentMethod === 'credit_card' ? 'border-teal-500 bg-teal-50 text-teal-700' : 'border-slate-200 bg-white hover:border-slate-300'}`}
>
<CreditCard className={`w-8 h-8 ${paymentMethod === 'credit_card' ? 'text-teal-600' : 'text-slate-400'}`} />
<span className="font-bold text-sm">Kartu Kredit</span>
</div>
<div
onClick={() => setPaymentMethod('ewallet')}
className={`p-4 rounded-xl border-2 cursor-pointer flex flex-col items-center gap-3 transition-all ${paymentMethod === 'ewallet' ? 'border-teal-500 bg-teal-50 text-teal-700' : 'border-slate-200 bg-white hover:border-slate-300'}`}
>
<Wallet className={`w-8 h-8 ${paymentMethod === 'ewallet' ? 'text-teal-600' : 'text-slate-400'}`} />
<span className="font-bold text-sm">E-Wallet</span>
</div>
</div>
{/* Conditional Form Inputs */}
{paymentMethod === 'credit_card' ? (
<div className="space-y-6 animate-fade-in-up">
<div>
<label className="block text-sm font-bold text-slate-700 mb-2">Nomor Kartu</label>
<div className="relative">
<input type="text" className="w-full px-4 py-3 pl-12 rounded-xl border border-slate-200 focus:border-teal-500 focus:ring-2 focus:ring-teal-200 outline-none transition-all" placeholder="0000 0000 0000 0000" />
<CreditCard className="w-5 h-5 text-slate-400 absolute left-4 top-1/2 -translate-y-1/2" />
</div>
</div>
<div className="grid grid-cols-2 gap-6">
<div>
<label className="block text-sm font-bold text-slate-700 mb-2">Masa Berlaku (MM/YY)</label>
<input type="text" className="w-full px-4 py-3 rounded-xl border border-slate-200 focus:border-teal-500 focus:ring-2 focus:ring-teal-200 outline-none transition-all" placeholder="12/26" />
</div>
<div>
<label className="block text-sm font-bold text-slate-700 mb-2">CVC</label>
<input type="text" className="w-full px-4 py-3 rounded-xl border border-slate-200 focus:border-teal-500 focus:ring-2 focus:ring-teal-200 outline-none transition-all" placeholder="123" />
</div>
</div>
</div>
) : (
<div className="space-y-6 animate-fade-in-up p-6 bg-slate-50 rounded-xl border border-slate-200">
<label className="block text-sm font-bold text-slate-700 mb-2 text-center">Pilih E-Wallet</label>
<div className="grid grid-cols-3 gap-4 mb-2">
<div
onClick={() => setSelectedEWallet('gopay')}
className={`p-3 rounded-xl border-2 cursor-pointer flex items-center justify-center bg-white transition-all ${selectedEWallet === 'gopay' ? 'border-teal-500 ring-2 ring-teal-200' : 'border-slate-200 hover:border-teal-300'}`}
>
<img src="/images/gopay.svg" alt="GoPay" className="h-6 object-contain" />
</div>
<div
onClick={() => setSelectedEWallet('ovo')}
className={`p-3 rounded-xl border-2 cursor-pointer flex items-center justify-center bg-white transition-all ${selectedEWallet === 'ovo' ? 'border-teal-500 ring-2 ring-teal-200' : 'border-slate-200 hover:border-teal-300'}`}
>
<img src="/images/ovo.png" alt="OVO" className="h-6 object-contain" />
</div>
<div
onClick={() => setSelectedEWallet('shopeepay')}
className={`p-3 rounded-xl border-2 cursor-pointer flex items-center justify-center bg-white transition-all ${selectedEWallet === 'shopeepay' ? 'border-teal-500 ring-2 ring-teal-200' : 'border-slate-200 hover:border-teal-300'}`}
>
<img src="/images/shopeepay.png" alt="ShopeePay" className="h-6 object-contain" />
</div>
</div>
<p className="text-sm text-slate-600 font-medium text-center mt-4">
Anda akan diarahkan ke aplikasi <strong className="text-slate-900 capitalize">{selectedEWallet === 'gopay' ? 'Gojek' : selectedEWallet === 'ovo' ? 'OVO' : 'Shopee'}</strong> untuk menghubungkan akun {selectedEWallet === 'gopay' ? 'GoPay' : selectedEWallet === 'ovo' ? 'OVO' : 'ShopeePay'} Anda sebagai metode pembayaran langganan.
</p>
</div>
)}
<div className="mt-8 flex items-start gap-3 bg-teal-50 p-4 rounded-xl border border-teal-100">
<Lock className="w-5 h-5 text-teal-600 flex-shrink-0 mt-0.5" />
<p className="text-xs text-teal-800 leading-relaxed font-medium">
Kami menggunakan sistem enkripsi tingkat bank yang diproses secara aman oleh <strong>Xendit</strong>. Detail pembayaran Anda tersimpan aman. Anda tidak akan dikenakan biaya apapun hari ini.
</p>
</div>
</div>
</div>
{/* Right Column: Order Summary */}
<div className="lg:col-span-1">
<div className="bg-white p-6 rounded-3xl border border-slate-200 shadow-xl sticky top-28">
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-red-50 text-red-600 font-bold text-xs mb-6 border border-red-100">
<Sparkles className="w-4 h-4" /> 1 Bulan Gratis
</div>
<h3 className="text-xl font-extrabold text-slate-900 mb-6 pb-6 border-b border-slate-100">Ringkasan Pesanan</h3>
<div className="space-y-4 mb-6">
<div className="flex justify-between items-center">
<span className="font-bold text-slate-700">Paket {plan.name}</span>
<span className="font-medium text-slate-600">Rp {(plan.price).toLocaleString('id-ID')}</span>
</div>
<div className="flex justify-between items-center text-red-600">
<span className="font-bold">Promo 1 Bulan Gratis</span>
<span className="font-bold">- Rp {(plan.price).toLocaleString('id-ID')}</span>
</div>
</div>
<div className="pt-6 border-t border-slate-100 mb-6">
<div className="flex justify-between items-end mb-2">
<span className="text-sm font-bold text-slate-500 uppercase tracking-widest">Total Hari Ini</span>
<span className="text-3xl font-black text-teal-600">Rp 0</span>
</div>
</div>
<div className="bg-slate-50 p-4 rounded-xl border border-slate-200 mb-8">
<p className="text-xs text-slate-600 leading-relaxed font-medium">
Penagihan normal sebesar <strong className="text-slate-900">Rp {(plan.price).toLocaleString('id-ID')} / bulan</strong> akan dimulai pada tanggal <strong className="text-slate-900">{getNextMonthDate()}</strong>. Anda dapat membatalkan langganan kapan saja sebelum tanggal tersebut.
</p>
</div>
<button className="w-full bg-teal-600 text-white py-4 rounded-xl font-black text-lg shadow-lg hover:bg-teal-700 transition-all flex justify-center items-center gap-2">
Mulai Uji Coba Gratis
</button>
</div>
</div>
</div>
</div>
</div>
);
};
return (
<div className="min-h-screen font-sans text-slate-800 bg-white">
{renderNavbar()}
<main>
{currentPage === 'home' ? renderHomePage() : renderIndustryDetail()}
</main>
<main className="flex-1">
{currentPage === 'home' && renderHomePage()}
{currentPage === 'industry-detail' && renderIndustryDetail()}
{currentPage === 'checkout' && renderCheckoutPage()}
{/* MOBILE MENU DRAWER OUTSIDE NAVBAR */}
{isMenuOpen && (
<div
className="fixed inset-0 z-[10000] bg-white flex flex-col md:hidden"
style={{ backgroundColor: 'white', opacity: 1 }}
>
<div className="flex justify-between items-center h-20 px-4 border-b border-slate-100 flex-shrink-0">
<div className="flex items-center gap-2">
<img src="/images/logokulakpos.png" alt="Kulakpos" className="h-8 w-auto" />
</div>
<button onClick={() => setIsMenuOpen(false)} className="text-slate-600 p-2 border border-slate-200 rounded-lg">
<X className="h-7 w-7" />
</button>
</div>
<div className="flex-1 overflow-y-auto py-4 px-6 bg-white">
<div className="flex flex-col gap-4 font-bold text-base text-slate-800">
<a href="/#fitur" onClick={() => { setIsMenuOpen(false); setCurrentPage('home'); setTimeout(() => document.getElementById('fitur')?.scrollIntoView({behavior: 'smooth'}), 100); }} className="pb-3 border-b border-slate-100 flex items-center justify-between">Fitur POS <ChevronRight className="w-4 h-4 text-slate-300" /></a>
<a href="/#ai" onClick={() => { setIsMenuOpen(false); setCurrentPage('home'); setTimeout(() => document.getElementById('ai')?.scrollIntoView({behavior: 'smooth'}), 100); }} className="pb-3 border-b border-slate-100 flex items-center justify-between text-teal-600 font-extrabold">AI Assistance <Bot className="w-4 h-4" /></a>
<a href="/#perangkat" onClick={() => { setIsMenuOpen(false); setCurrentPage('home'); setTimeout(() => document.getElementById('perangkat')?.scrollIntoView({behavior: 'smooth'}), 100); }} className="pb-3 border-b border-slate-100 flex items-center justify-between">Perangkat <ChevronRight className="w-4 h-4 text-slate-300" /></a>
<a href="/#solusi" onClick={() => { setIsMenuOpen(false); setCurrentPage('home'); setTimeout(() => document.getElementById('solusi')?.scrollIntoView({behavior: 'smooth'}), 100); }} className="pb-3 border-b border-slate-100 flex items-center justify-between">Solusi Industri <ChevronRight className="w-4 h-4 text-slate-300" /></a>
<a href="/#harga" onClick={() => { setIsMenuOpen(false); setCurrentPage('home'); setTimeout(() => document.getElementById('harga')?.scrollIntoView({behavior: 'smooth'}), 100); }} className="pb-3 border-b border-slate-100 flex items-center justify-between">Harga <ChevronRight className="w-4 h-4 text-slate-300" /></a>
<div className="grid grid-cols-2 gap-3 mt-2">
<a href="/login" className="text-center py-3 rounded-lg border border-slate-200 text-slate-700 text-sm">Masuk</a>
<a href="/plans" className="text-center py-3 rounded-lg bg-teal-600 text-white shadow-md text-sm">Coba Gratis</a>
</div>
</div>
</div>
<div className="p-6 border-t border-slate-100 bg-slate-50 text-center text-xs text-slate-500 font-medium flex-shrink-0">
© {new Date().getFullYear()} PT Sada Teknologi Indonesia.
</div>
</div>
)}
</main>
{renderFooter()}
{/* WHATSAPP WIDGET */}
<a
href="https://wa.me/6281234567890?text=Halo%20Kulakpos.id,%20saya%20tertarik%20dengan%20solusi%20POS%20dan%20AI%20Assistance%20Anda."
href="https://wa.me/6287783275818?text=Halo%20Kulakpos.id,%20saya%20tertarik%20dengan%20solusi%20POS%20dan%20AI%20Assistance%20Anda."
target="_blank"
rel="noopener noreferrer"
className="fixed bottom-6 right-6 z-[60] group flex items-center gap-3"

View File

@@ -62,7 +62,7 @@ class="form-control" placeholder="{{ __('Enter Link') }}">
<label>{{ __('WhatsApp Number (For Floating Button)') }}</label>
<input type="text" name="whatsapp_number"
value="{{ $general->value['whatsapp_number'] ?? '' }}" class="form-control"
placeholder="{{ __('e.g., 6289620324323') }}">
placeholder="{{ __('e.g., 6287783275818') }}">
</div>
<div class="col-lg-6">

View File

@@ -2,7 +2,9 @@
<html lang="en">
<head>
<meta charset="UTF-8">
<title>KulakPOS</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Kulakpos.id - Solusi Kasir Pintar (SaaS POS) Terlengkap dengan Teknologi AI Assistance untuk UMKM Naik Kelas.">
<title>KulakPOS | Solusi Kasir Pintar Berbasis AI</title>
@vite('resources/js/app.jsx')
</head>
<body>

View File

@@ -4,7 +4,7 @@
<div class="row mt-5">
<div class="col-md-6 col-lg-5">
<a href="{{ route('home') }}">
<img src="{{ asset($general->value['footer_logo'] ?? '') }}" alt="footer-logo" class="footer-logo" />
<img src="/images/logokulakpos.png" alt="footer-logo" class="footer-logo" style="height: 40px; width: auto;" />
</a>
<p class="mt-3 footer-details">
{{ $page_data['headings']['footer_short_title'] ?? '' }}

View File

@@ -114,12 +114,8 @@
<nav class="navbar navbar-expand-lg h-100" style="min-height:80px;">
<div class="container" style="max-width:80rem;">
<!-- Logo -->
<a class="navbar-brand d-flex align-items-center m-0 p-0" href="{{ route('home') }}">
<div class="rh-logo-icon">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m2 7 4.41-4.41A2 2 0 0 1 7.83 2h8.34a2 2 0 0 1 1.42.59L22 7"/><path d="M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8"/><path d="M15 22v-4a2 2 0 0 0-2-2h-2a2 2 0 0 0-2 2v4"/><path d="M2 7h20"/><path d="M22 7v3a2 2 0 0 1-2 2v0a2.7 2.7 0 0 1-1.59-.63.7.7 0 0 0-.82 0A2.7 2.7 0 0 1 16 12a2.7 2.7 0 0 1-1.59-.63.7.7 0 0 0-.82 0A2.7 2.7 0 0 1 12 12a2.7 2.7 0 0 1-1.59-.63.7.7 0 0 0-.82 0A2.7 2.7 0 0 1 8 12a2.7 2.7 0 0 1-1.59-.63.7.7 0 0 0-.82 0A2.7 2.7 0 0 1 4 12v0a2 2 0 0 1-2-2V7"/></svg>
</div>
<!-- Hide text on mobile using d-none d-sm-block -->
<span class="rh-logo-text d-none d-md-block">Kulak<span>pos</span></span>
<a href="{{ route('home') }}">
<img src="/images/logokulakpos.png" alt="footer-logo" class="footer-logo" style="height: 40px; width: auto;" />
</a>
<!-- Mobile Toggle -->

View File

@@ -113,7 +113,7 @@ class="form-select w-auto country-input" required>
</select>
<input type="text" name="phone" id="phone"
class="form-control number-input w-100"
placeholder="{{ __('Enter your phone number') }}" required>
placeholder="{{ __('e.g., 6287783275818') }}" required>
</div>
</td>
</tr>
@@ -165,7 +165,7 @@ class="btn btn-md payment-btn">{{ __('Pay Now') }}</button>
<!-- Floating WhatsApp Button -->
@php
$whatsapp_number = \App\Models\Option::where('key', 'general')->first()->value['whatsapp_number'] ?? '6289620324323';
$whatsapp_number = \App\Models\Option::where('key', 'general')->first()->value['whatsapp_number'] ?? '6287783275818';
@endphp
@if($whatsapp_number)
<a href="https://wa.me/{{ $whatsapp_number }}?text=Halo%20Admin,%20saya%20butuh%20bantuan%20terkait%20pembayaran"

21
server.php Normal file
View File

@@ -0,0 +1,21 @@
<?php
/**
* Laravel - A PHP Framework For Web Artisans
*
* @package Laravel
* @author Taylor Otwell <taylor@laravel.com>
*/
$uri = urldecode(
parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH)
);
// This file allows us to emulate Apache's "mod_rewrite" functionality from the
// built-in PHP web server. This provides a convenient way to test a Laravel
// application without having installed a "real" web server software here.
if ($uri !== '/' && file_exists(__DIR__.'/public'.$uri)) {
return false;
}
require_once __DIR__.'/public/index.php';

View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) AgeekDev
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -0,0 +1,74 @@
{
"name": "ageekdev/laravel-barcode",
"description": "laravel barcode generator",
"keywords": [
"ageekdev",
"laravel",
"barcode"
],
"homepage": "https://github.com/ageekdev/laravel-barcode",
"license": "MIT",
"authors": [
{
"name": "Tint Naing Win",
"email": "amigo.k8@gmail.com",
"role": "Developer"
}
],
"require": {
"php": "^8.1",
"ext-bcmath": "*",
"illuminate/contracts": "^9.0|^10.0|^11.0|^12.0",
"spatie/color": "^1.5"
},
"require-dev": {
"laravel/pint": "^1.5",
"larastan/larastan": "^2.0|^3.0",
"orchestra/testbench": "^7.31|^8.11|^9.0|^10.0",
"pestphp/pest": "^1.21|^2.0|^3.0",
"pestphp/pest-plugin-laravel": "^1.4|^2.0|^3.0",
"phpstan/extension-installer": "^1.1",
"phpstan/phpstan-deprecation-rules": "^1.0|^2.0",
"phpstan/phpstan-phpunit": "^1.0|^2.0",
"roave/security-advisories": "dev-latest"
},
"autoload": {
"psr-4": {
"AgeekDev\\Barcode\\": "src"
}
},
"autoload-dev": {
"psr-4": {
"AgeekDev\\Barcode\\Tests\\": "tests"
}
},
"suggest": {
"ext-gd": "For JPG and PNG generators, GD or Imagick is required",
"ext-imagick": "For JPG and PNG generators, GD or Imagick is required"
},
"scripts": {
"analyse": "vendor/bin/phpstan analyse",
"test": "vendor/bin/pest",
"test-coverage": "vendor/bin/pest --coverage",
"format": "vendor/bin/pint"
},
"config": {
"sort-packages": true,
"allow-plugins": {
"pestphp/pest-plugin": true,
"phpstan/extension-installer": true
}
},
"extra": {
"laravel": {
"providers": [
"AgeekDev\\Barcode\\BarcodeServiceProvider"
],
"aliases": {
"Barcode": "AgeekDev\\Barcode\\Facades\\Barcode"
}
}
},
"minimum-stability": "dev",
"prefer-stable": true
}

View File

@@ -0,0 +1,47 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Default Barcode Image Type
|--------------------------------------------------------------------------
|
| This option controls the default barcode generator that will be used
| to generate barcode. Alternative barcode generators may be setup and used as
| needed; however, this generator will be used by default.
|
| Supported: "dynamic_html", "html", "jpg", "png", "svg"
|
*/
'image_type' => env('BARCODE_DRIVER', 'png'),
/*
|----------------------------------------------------------------------------------------------
| Accepted Barcode Types
|----------------------------------------------------------------------------------------------
| Supported: TYPE_CODE_32, TYPE_CODE_39, TYPE_CODE_39_CHECKSUM, TYPE_CODE_39E,
| TYPE_CODE_39E_CHECKSUM, TYPE_CODE_93, TYPE_STANDARD_2_5, TYPE_STANDARD_2_5_CHECKSUM,
| TYPE_INTERLEAVED_2_5, TYPE_INTERLEAVED_2_5_CHECKSUM, TYPE_CODE_128, TYPE_CODE_128_A,
| TYPE_CODE_128_B, TYPE_CODE_128_C, TYPE_EAN_2, TYPE_EAN_5, TYPE_EAN_8, TYPE_EAN_13,
| TYPE_UPC_A, TYPE_UPC_E, TYPE_MSI, TYPE_MSI_CHECKSUM, TYPE_POSTNET, TYPE_PLANET, TYPE_RMS4CC,
| TYPE_KIX, TYPE_IMB, TYPE_CODABAR, TYPE_CODE_11, TYPE_PHARMA_CODE, TYPE_PHARMA_CODE_TWO_TRACKS
|
*/
'type' => \AgeekDev\Barcode\Enums\BarcodeType::CODE_128,
/*
* Foreground color of the barcode
*/
'foreground_color' => '#000000',
/*
* width factor of barcode
*/
'width_factor' => 2,
/*
* Height of image barcode
*/
'height' => 30,
];

View File

@@ -0,0 +1,68 @@
<?php
namespace AgeekDev\Barcode;
use AgeekDev\Barcode\Contracts\ImageType;
use AgeekDev\Barcode\Enums\BarcodeType;
use AgeekDev\Barcode\Enums\Type;
use AgeekDev\Barcode\Types\TypeInterface;
abstract class AbstractGenerator implements ImageType
{
/**
* The foreground color of the barcode.
*/
protected string|array $foregroundColor = '#000000';
protected int $height;
protected int $widthFactor;
protected BarcodeType|Type $type;
public function __construct()
{
$this->foregroundColor = config('barcode.foreground_color');
$this->widthFactor = config('barcode.width_factor');
$this->height = config('barcode.height');
$this->type = config('barcode.type');
}
protected function getBarcodeData(string $code, BarcodeType|Type $type): Barcode
{
return $this->createDataBuilderForType($type)->getBarcodeData($code);
}
protected function createDataBuilderForType(BarcodeType|Type $type): TypeInterface
{
return $type->class();
}
public function type(BarcodeType|Type $type): static
{
$this->type = $type;
return $this;
}
public function foregroundColor(string $foregroundColor): static
{
$this->foregroundColor = $foregroundColor;
return $this;
}
public function height(int $height): static
{
$this->height = $height;
return $this;
}
public function widthFactor(int $widthFactor): static
{
$this->widthFactor = $widthFactor;
return $this;
}
}

View File

@@ -0,0 +1,61 @@
<?php
namespace AgeekDev\Barcode;
class Barcode
{
protected string $barcode;
protected int $width = 0;
protected int $height = 0;
protected array $bars = [];
public function __construct(string $barcode)
{
$this->barcode = $barcode;
}
/**
* Add bar.
*/
public function addBar(BarcodeBar $bar): void
{
$this->bars[] = $bar;
$this->width += $bar->getWidth();
$this->height = max($this->height, $bar->getHeight());
}
/**
* Get the barcode.
*/
public function getBarcode(): string
{
return $this->barcode;
}
/**
* Get the width.
*/
public function getWidth(): int
{
return $this->width;
}
/**
* Get the height.
*/
public function getHeight(): int
{
return $this->height;
}
/**
* Get the bars.
*/
public function getBars(): array
{
return $this->bars;
}
}

View File

@@ -0,0 +1,58 @@
<?php
namespace AgeekDev\Barcode;
class BarcodeBar
{
protected int $width;
protected int $height;
protected int $positionVertical;
protected int $type;
public const TYPE_BAR = 1;
public const TYPE_SPACING = 0;
public function __construct(int $width, int $height, bool $drawBar = true, int $positionVertical = 0)
{
$this->width = $width;
$this->height = $height;
$this->positionVertical = $positionVertical;
$this->type = $drawBar ? self::TYPE_BAR : self::TYPE_SPACING;
}
/**
* Get the width.
*/
public function getWidth(): int
{
return $this->width;
}
/**
* Get the height.
*/
public function getHeight(): int
{
return $this->height;
}
/**
* Get position vertical.
*/
public function getPositionVertical(): int
{
return $this->positionVertical;
}
/**
* Bar or not.
*/
public function isBar(): bool
{
return $this->type === self::TYPE_BAR;
}
}

View File

@@ -0,0 +1,277 @@
<?php
namespace AgeekDev\Barcode;
use AgeekDev\Barcode\Contracts\Factory;
use AgeekDev\Barcode\Contracts\ImageType;
use AgeekDev\Barcode\Drivers\DynamicHTML;
use AgeekDev\Barcode\Drivers\HTML;
use AgeekDev\Barcode\Drivers\JPG;
use AgeekDev\Barcode\Drivers\PNG;
use AgeekDev\Barcode\Drivers\SVG;
use AgeekDev\Barcode\Enums\BarcodeType;
use AgeekDev\Barcode\Enums\Type;
use AgeekDev\Barcode\Exceptions\BarcodeException;
use Closure;
use Illuminate\Contracts\Container\BindingResolutionException;
use Illuminate\Contracts\Container\Container;
use Illuminate\Support\Str;
use InvalidArgumentException;
class BarcodeManager implements Factory
{
/**
* The container instance.
*/
protected Container $container;
/**
* The configuration repository instance.
*
* @var \Illuminate\Contracts\Config\Repository
*/
protected mixed $config;
/**
* The registered custom driver creators.
*/
protected array $customCreators = [];
/**
* The array of created "drivers".
*/
protected array $imageTypes = [];
/**
* Create a new manager instance.
*
* @throws BindingResolutionException
*/
public function __construct(Container $container)
{
$this->container = $container;
$this->config = $container->make('config');
}
/**
* Get a image type instance.
*/
public function imageType(?string $name = null): ImageType
{
$name = $name ?: $this->getDefaultDriver();
// If the given driver has not been created before, we will create the instances
// here and cache it, so we can return it next time very quickly. If there is
// already a driver created by this name, we'll just return that instance.
if (! isset($this->imageTypes[$name])) {
$this->imageTypes[$name] = $this->createDriver($name);
}
return $this->imageTypes[$name];
}
/**
* Create a new driver instance.
*
* @throws InvalidArgumentException
*/
protected function createDriver(string $driver): ImageType
{
// First, we will determine if a custom driver creator exists for the given driver and
// if it does not we will check for a creator method for the driver. Custom creator
// callbacks allow developers to build their own "drivers" easily using Closures.
if (isset($this->customCreators[$driver])) {
return $this->callCustomCreator($driver);
}
$method = 'create'.Str::studly($driver).'Driver';
if (method_exists($this, $method)) {
return $this->$method();
}
throw new InvalidArgumentException("ImageType [$driver] not supported.");
}
/**
* Call a custom driver creator.
*/
protected function callCustomCreator(string $driver): ImageType
{
return $this->customCreators[$driver]($this->container);
}
/**
* Create an HTML instance.
*/
public function createHtmlDriver(): ImageType
{
return new HTML;
}
/**
* Create an DynamicHTML instance.
*/
public function createDynamicHtmlDriver(): ImageType
{
return new DynamicHTML;
}
/**
* Create an JPG instance.
*/
public function createJpgDriver(): ImageType
{
return new JPG;
}
/**
* Create an PNG instance.
*/
public function createPngDriver(): ImageType
{
return new PNG;
}
/**
* Create an SVG instance.
*/
public function createSvgDriver(): ImageType
{
return new SVG;
}
/**
* Generate Barcode.
*/
public function generate(string $text): string
{
return $this->imageType()->generate($text);
}
/**
* Set the barcode type.
*/
public function type(BarcodeType|Type $type): ImageType
{
return $this->imageType()->type($type);
}
/**
* Set the barcode foreground color.
*/
public function foregroundColor(string $foregroundColor): ImageType
{
return $this->imageType()->foregroundColor($foregroundColor);
}
/**
* Set the barcode height.
*/
public function height(int $height): ImageType
{
return $this->imageType()->height($height);
}
/**
* Set the barcode width factor.
*/
public function widthFactor(int $widthFactor): ImageType
{
return $this->imageType()->widthFactor($widthFactor);
}
/**
* Force the use of Imagick image extension
*
* @throws BarcodeException
*/
public function useImagick(): ImageType
{
if (method_exists($this->imageType(), 'useImagick')) {
if (! extension_loaded('imagick')) {
throw new BarcodeException('The imagick is not installed!');
}
return $this->imageType()->useImagick();
}
throw new BarcodeException('This image type does not support useImagick function.');
}
/**
* Force the use of the GD image library
*
* @throws BarcodeException
*/
public function useGd(): ImageType
{
if (method_exists($this->imageType(), 'useGd')) {
if (! function_exists('imagecreate')) {
throw new BarcodeException('The GD is not installed!');
}
return $this->imageType()->useGd();
}
throw new BarcodeException('This image type does not support useGd function.');
}
/**
* Get the default driver name.
*/
public function getDefaultDriver(): string
{
return $this->config->get('barcode.image_type', 'png');
}
/**
* Register a custom driver creator Closure.
*/
public function extend(string $imageType, Closure $callback): self
{
$this->customCreators[$imageType] = $callback;
return $this;
}
/**
* Get the container instance used by the manager.
*/
public function getContainer(): Container
{
return $this->container;
}
/**
* Set the container instance used by the manager.
*/
public function setContainer(Container $container): self
{
$this->container = $container;
return $this;
}
/**
* Unset the given disk instances.
*/
public function forgetImageType(array|string $imageType): self
{
foreach ((array) $imageType as $imageTypeName) {
unset($this->imageTypes[$imageTypeName]);
}
return $this;
}
/**
* Dynamically call the default driver instance.
*
* @return mixed
*/
public function __call(string $method, array $parameters)
{
return $this->imageType()->$method(...$parameters);
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace AgeekDev\Barcode;
use Illuminate\Support\ServiceProvider;
class BarcodeServiceProvider extends ServiceProvider
{
/**
* Register the service provider.
*/
public function register(): void
{
$this->mergeConfigFrom(__DIR__.'/../config/barcode.php', 'barcode');
$this->app->bind('laravel-barcode', function ($app) {
return new BarcodeManager($app);
});
}
public function boot(): void
{
$this->publishes([
__DIR__.'/../config/barcode.php' => config_path('barcode.php'),
], 'laravel-barcode-config');
}
}

View File

@@ -0,0 +1,11 @@
<?php
namespace AgeekDev\Barcode\Contracts;
interface Factory
{
/**
* Get image type implementation.
*/
public function imageType(?string $name = null): ImageType;
}

View File

@@ -0,0 +1,19 @@
<?php
namespace AgeekDev\Barcode\Contracts;
use AgeekDev\Barcode\Enums\BarcodeType;
use AgeekDev\Barcode\Enums\Type;
interface ImageType
{
public function generate(string $text): string;
public function type(BarcodeType|Type $type): static;
public function foregroundColor(string $foregroundColor): static;
public function height(int $height): static;
public function widthFactor(int $widthFactor): static;
}

View File

@@ -0,0 +1,44 @@
<?php
namespace AgeekDev\Barcode\Drivers;
use AgeekDev\Barcode\AbstractGenerator;
use AgeekDev\Barcode\BarcodeBar;
class DynamicHTML extends AbstractGenerator
{
private const WIDTH_PRECISION = 6;
/**
* Return an HTML representation of barcode.
* This 'dynamic' version uses percentage based widths and heights, resulting in a vector-y qualitative result.
*
* @param string $text code to print
*/
public function generate(string $text): string
{
$barcodeData = $this->getBarcodeData($text, $this->type);
$html = '<div style="font-size:0;position:relative;width:100%;height:100%">'.PHP_EOL;
$positionHorizontal = 0;
/** @var BarcodeBar $bar */
foreach ($barcodeData->getBars() as $bar) {
$barWidth = $bar->getWidth() / $barcodeData->getWidth() * 100;
$barHeight = round(($bar->getHeight() / $barcodeData->getHeight() * 100), 3);
if ($barWidth > 0 && $bar->isBar()) {
$positionVertical = round(($bar->getPositionVertical() / $barcodeData->getHeight() * 100), 3);
// draw a vertical bar
$html .= '<div style="background-color:'.$this->foregroundColor.';width:'.round($barWidth, self::WIDTH_PRECISION).'%;height:'.$barHeight.'%;position:absolute;left:'.round($positionHorizontal, self::WIDTH_PRECISION).'%;top:'.$positionVertical.(($positionVertical > 0) ? '%' : '').'">&nbsp;</div>'.PHP_EOL;
}
$positionHorizontal += $barWidth;
}
$html .= '</div>'.PHP_EOL;
return $html;
}
}

View File

@@ -0,0 +1,39 @@
<?php
namespace AgeekDev\Barcode\Drivers;
use AgeekDev\Barcode\AbstractGenerator;
use AgeekDev\Barcode\BarcodeBar;
class HTML extends AbstractGenerator
{
/**
* Return an HTML representation of barcode.
* This original version uses pixel based widths and heights. Use Dynamic HTML version for better quality representation.
*/
public function generate(string $text): string
{
$barcodeData = $this->getBarcodeData($text, $this->type);
$html = '<div style="font-size:0;position:relative;width:'.($barcodeData->getWidth() * $this->widthFactor).'px;height:'.($this->height).'px;">'.PHP_EOL;
$positionHorizontal = 0;
/** @var BarcodeBar $bar */
foreach ($barcodeData->getBars() as $bar) {
$barWidth = round(($bar->getWidth() * $this->widthFactor), 3);
$barHeight = round(($bar->getHeight() * $this->height / $barcodeData->getHeight()), 3);
if ($barWidth > 0 && $bar->isBar()) {
$positionVertical = round(($bar->getPositionVertical() * $this->height / $barcodeData->getHeight()), 3);
$html .= '<div style="background-color:'.$this->foregroundColor.';width:'.$barWidth.'px;height:'.$barHeight.'px;position:absolute;left:'.$positionHorizontal.'px;top:'.$positionVertical.(($positionVertical > 0) ? 'px' : '').'">&nbsp;</div>'.PHP_EOL;
}
$positionHorizontal += $barWidth;
}
$html .= '</div>'.PHP_EOL;
return $html;
}
}

View File

@@ -0,0 +1,22 @@
<?php
namespace AgeekDev\Barcode\Drivers;
use Imagick;
class JPG extends PNG
{
protected function createImagickImageObject(int $width, int $height): Imagick
{
$image = new Imagick;
$image->newImage($width, $height, 'white', 'JPG');
return $image;
}
protected function generateGdImage($image): void
{
imagejpeg($image);
imagedestroy($image);
}
}

View File

@@ -0,0 +1,141 @@
<?php
namespace AgeekDev\Barcode\Drivers;
use AgeekDev\Barcode\AbstractGenerator;
use AgeekDev\Barcode\BarcodeBar;
use AgeekDev\Barcode\Exceptions\BarcodeException;
use Imagick;
use imagickdraw;
use imagickpixel;
use Spatie\Color\Hex;
class PNG extends AbstractGenerator
{
protected bool $useImagick = true;
/**
* @throws BarcodeException
*/
public function __construct()
{
parent::__construct();
// Auto switch between GD and Imagick based on what is installed
if (extension_loaded('imagick')) {
$this->useImagick = true;
} elseif (function_exists('imagecreate')) {
$this->useImagick = false;
} else {
throw new BarcodeException('Neither gd-lib or imagick are installed!');
}
}
/**
* Force the use of Imagick image extension.
*/
public function useImagick(): self
{
$this->useImagick = true;
return $this;
}
/**
* Force the use of the GD image library.
*/
public function useGd(): self
{
$this->useImagick = false;
return $this;
}
/**
* @param string $text code to print
*
* @throws \ImagickDrawException
* @throws \ImagickException
* @throws \ImagickPixelException
*/
public function generate(string $text): string
{
$barcodeData = $this->getBarcodeData($text, $this->type);
$width = round($barcodeData->getWidth() * $this->widthFactor);
$foregroundColor = $this->getForegroundColor();
if ($this->useImagick) {
$imagickBarsShape = new imagickdraw;
$imagickBarsShape->setFillColor(new imagickpixel('rgb('.implode(',', $foregroundColor).')'));
} else {
$image = $this->createGdImageObject($width, $this->height);
$gdForegroundColor = imagecolorallocate($image, $foregroundColor[0], $foregroundColor[1], $foregroundColor[2]);
}
// print bars
$positionHorizontal = 0;
/** @var BarcodeBar $bar */
foreach ($barcodeData->getBars() as $bar) {
$barWidth = round(($bar->getWidth() * $this->widthFactor), 3);
if ($barWidth > 0 && $bar->isBar()) {
$y = round(($bar->getPositionVertical() * $this->height / $barcodeData->getHeight()), 3);
$barHeight = round(($bar->getHeight() * $this->height / $barcodeData->getHeight()), 3);
// draw a vertical bar
if ($this->useImagick) {
$imagickBarsShape->rectangle($positionHorizontal, $y, ($positionHorizontal + $barWidth - 1), ($y + $barHeight));
} else {
imagefilledrectangle($image, $positionHorizontal, $y, ($positionHorizontal + $barWidth - 1), ($y + $barHeight), $gdForegroundColor);
}
}
$positionHorizontal += $barWidth;
}
if ($this->useImagick) {
$image = $this->createImagickImageObject($width, $this->height);
$image->drawImage($imagickBarsShape);
return $image->getImageBlob();
}
ob_start();
$this->generateGdImage($image);
return ob_get_clean();
}
public function getForegroundColor(): string|array
{
$color = Hex::fromString($this->foregroundColor)->toRgba();
return [$color->red(), $color->blue(), $color->green()];
}
protected function createGdImageObject(int $width, int $height)
{
$image = imagecreate($width, $height);
$colorBackground = imagecolorallocate($image, 255, 255, 255);
imagecolortransparent($image, $colorBackground);
return $image;
}
/**
* @throws \ImagickException
*/
protected function createImagickImageObject(int $width, int $height): Imagick
{
$image = new Imagick;
$image->newImage($width, $height, 'none', 'PNG');
return $image;
}
protected function generateGdImage($image): void
{
imagepng($image);
imagedestroy($image);
}
}

View File

@@ -0,0 +1,50 @@
<?php
namespace AgeekDev\Barcode\Drivers;
use AgeekDev\Barcode\AbstractGenerator;
use AgeekDev\Barcode\BarcodeBar;
class SVG extends AbstractGenerator
{
/**
* Return SVG string representation of barcode.
*
* @param string $text code to print
*/
public function generate(string $text): string
{
$barcodeData = $this->getBarcodeData($text, $this->type);
// replace table for special characters
$repstr = ["\0" => '', '&' => '&amp;', '<' => '&lt;', '>' => '&gt;'];
$width = round(($barcodeData->getWidth() * $this->widthFactor), 3);
$svg = '<?xml version="1.0" standalone="no" ?>'.PHP_EOL;
$svg .= '<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">'.PHP_EOL;
$svg .= '<svg width="'.$width.'" height="'.$this->height.'" viewBox="0 0 '.$width.' '.$this->height.'" version="1.1" xmlns="http://www.w3.org/2000/svg">'.PHP_EOL;
$svg .= "\t".'<desc>'.strtr($barcodeData->getBarcode(), $repstr).'</desc>'.PHP_EOL;
$svg .= "\t".'<g id="bars" fill="'.$this->foregroundColor.'" stroke="none">'.PHP_EOL;
// print bars
$positionHorizontal = 0;
/** @var BarcodeBar $bar */
foreach ($barcodeData->getBars() as $bar) {
$barWidth = round(($bar->getWidth() * $this->widthFactor), 3);
$barHeight = round(($bar->getHeight() * $this->height / $barcodeData->getHeight()), 3);
if ($bar->isBar() && $barWidth > 0) {
$positionVertical = round(($bar->getPositionVertical() * $this->height / $barcodeData->getHeight()), 3);
// draw a vertical bar
$svg .= "\t\t".'<rect x="'.$positionHorizontal.'" y="'.$positionVertical.'" width="'.$barWidth.'" height="'.$barHeight.'" />'.PHP_EOL;
}
$positionHorizontal += $barWidth;
}
$svg .= "\t</g>".PHP_EOL;
$svg .= '</svg>'.PHP_EOL;
return $svg;
}
}

View File

@@ -0,0 +1,168 @@
<?php
namespace AgeekDev\Barcode\Enums;
use AgeekDev\Barcode\Types\TypeCodabar;
use AgeekDev\Barcode\Types\TypeCode11;
use AgeekDev\Barcode\Types\TypeCode128;
use AgeekDev\Barcode\Types\TypeCode128A;
use AgeekDev\Barcode\Types\TypeCode128B;
use AgeekDev\Barcode\Types\TypeCode128C;
use AgeekDev\Barcode\Types\TypeCode32;
use AgeekDev\Barcode\Types\TypeCode39;
use AgeekDev\Barcode\Types\TypeCode39Checksum;
use AgeekDev\Barcode\Types\TypeCode39Extended;
use AgeekDev\Barcode\Types\TypeCode39ExtendedChecksum;
use AgeekDev\Barcode\Types\TypeCode93;
use AgeekDev\Barcode\Types\TypeEan13;
use AgeekDev\Barcode\Types\TypeEan8;
use AgeekDev\Barcode\Types\TypeIntelligentMailBarcode;
use AgeekDev\Barcode\Types\TypeInterface;
use AgeekDev\Barcode\Types\TypeInterleaved25;
use AgeekDev\Barcode\Types\TypeInterleaved25Checksum;
use AgeekDev\Barcode\Types\TypeKix;
use AgeekDev\Barcode\Types\TypeMsi;
use AgeekDev\Barcode\Types\TypeMsiChecksum;
use AgeekDev\Barcode\Types\TypePharmacode;
use AgeekDev\Barcode\Types\TypePharmacodeTwoCode;
use AgeekDev\Barcode\Types\TypePlanet;
use AgeekDev\Barcode\Types\TypePostnet;
use AgeekDev\Barcode\Types\TypeRms4cc;
use AgeekDev\Barcode\Types\TypeStandard2of5;
use AgeekDev\Barcode\Types\TypeStandard2of5Checksum;
use AgeekDev\Barcode\Types\TypeUpcA;
use AgeekDev\Barcode\Types\TypeUpcE;
use AgeekDev\Barcode\Types\TypeUpcExtension2;
use AgeekDev\Barcode\Types\TypeUpcExtension5;
enum BarcodeType: string
{
case CODE_32 = 'C32';
case CODE_39 = 'C39';
case CODE_39_CHECKSUM = 'C39+';
case CODE_39E = 'C39E'; // CODE 39 EXTENDED
case CODE_39E_CHECKSUM = 'C39E+'; // CODE 39 EXTENDED + CHECKSUM
case CODE_93 = 'C93';
case STANDARD_2_5 = 'S25';
case STANDARD_2_5_CHECKSUM = 'S25+';
case INTERLEAVED_2_5 = 'I25';
case INTERLEAVED_2_5_CHECKSUM = 'I25+';
case CODE_128 = 'C128';
case CODE_128_A = 'C128A';
case CODE_128_B = 'C128B';
case CODE_128_C = 'C128C';
case EAN_2 = 'EAN2'; // 2-Digits UPC-Based Extention
case EAN_5 = 'EAN5'; // 5-Digits UPC-Based Extention
case EAN_8 = 'EAN8';
case EAN_13 = 'EAN13';
case UPC_A = 'UPCA';
case UPC_E = 'UPCE';
case MSI = 'MSI'; // MSI (Variation of Plessey code)
case MSI_CHECKSUM = 'MSI+'; // MSI + CHECKSUM (modulo 11)
case POSTNET = 'POSTNET';
case PLANET = 'PLANET';
case RMS4CC = 'RMS4CC'; // RMS4CC (Royal Mail 4-state Customer Code) - CBC (Customer Bar Code)
case KIX = 'KIX'; // KIX (Klant index - Customer index)
case IMB = 'IMB'; // IMB - Intelligent Mail Barcode - Onecode - USPS-B-3200
case CODABAR = 'CODABAR';
case CODE_11 = 'CODE11';
case PHARMA_CODE = 'PHARMA';
case PHARMA_CODE_TWO_TRACKS = 'PHARMA2T';
public function class(): TypeInterface
{
return match ($this) {
self::CODE_32 => new TypeCode32,
self::CODE_39 => new TypeCode39,
self::CODE_39_CHECKSUM => new TypeCode39Checksum,
self::CODE_39E => new TypeCode39Extended,
self::CODE_39E_CHECKSUM => new TypeCode39ExtendedChecksum,
self::CODE_93 => new TypeCode93,
self::STANDARD_2_5 => new TypeStandard2of5,
self::STANDARD_2_5_CHECKSUM => new TypeStandard2of5Checksum,
self::INTERLEAVED_2_5 => new TypeInterleaved25,
self::INTERLEAVED_2_5_CHECKSUM => new TypeInterleaved25Checksum,
self::CODE_128 => new TypeCode128,
self::CODE_128_A => new TypeCode128A,
self::CODE_128_B => new TypeCode128B,
self::CODE_128_C => new TypeCode128C,
self::EAN_2 => new TypeUpcExtension2,
self::EAN_5 => new TypeUpcExtension5,
self::EAN_8 => new TypeEan8,
self::EAN_13 => new TypeEan13,
self::UPC_A => new TypeUpcA,
self::UPC_E => new TypeUpcE,
self::MSI => new TypeMsi,
self::MSI_CHECKSUM => new TypeMsiChecksum,
self::POSTNET => new TypePostnet,
self::PLANET => new TypePlanet,
self::RMS4CC => new TypeRms4cc,
self::KIX => new TypeKix,
self::IMB => new TypeIntelligentMailBarcode,
self::CODABAR => new TypeCodabar,
self::CODE_11 => new TypeCode11,
self::PHARMA_CODE => new TypePharmacode,
self::PHARMA_CODE_TWO_TRACKS => new TypePharmacodeTwoCode,
};
}
}

View File

@@ -0,0 +1,171 @@
<?php
namespace AgeekDev\Barcode\Enums;
use AgeekDev\Barcode\Types\TypeCodabar;
use AgeekDev\Barcode\Types\TypeCode11;
use AgeekDev\Barcode\Types\TypeCode128;
use AgeekDev\Barcode\Types\TypeCode128A;
use AgeekDev\Barcode\Types\TypeCode128B;
use AgeekDev\Barcode\Types\TypeCode128C;
use AgeekDev\Barcode\Types\TypeCode32;
use AgeekDev\Barcode\Types\TypeCode39;
use AgeekDev\Barcode\Types\TypeCode39Checksum;
use AgeekDev\Barcode\Types\TypeCode39Extended;
use AgeekDev\Barcode\Types\TypeCode39ExtendedChecksum;
use AgeekDev\Barcode\Types\TypeCode93;
use AgeekDev\Barcode\Types\TypeEan13;
use AgeekDev\Barcode\Types\TypeEan8;
use AgeekDev\Barcode\Types\TypeIntelligentMailBarcode;
use AgeekDev\Barcode\Types\TypeInterface;
use AgeekDev\Barcode\Types\TypeInterleaved25;
use AgeekDev\Barcode\Types\TypeInterleaved25Checksum;
use AgeekDev\Barcode\Types\TypeKix;
use AgeekDev\Barcode\Types\TypeMsi;
use AgeekDev\Barcode\Types\TypeMsiChecksum;
use AgeekDev\Barcode\Types\TypePharmacode;
use AgeekDev\Barcode\Types\TypePharmacodeTwoCode;
use AgeekDev\Barcode\Types\TypePlanet;
use AgeekDev\Barcode\Types\TypePostnet;
use AgeekDev\Barcode\Types\TypeRms4cc;
use AgeekDev\Barcode\Types\TypeStandard2of5;
use AgeekDev\Barcode\Types\TypeStandard2of5Checksum;
use AgeekDev\Barcode\Types\TypeUpcA;
use AgeekDev\Barcode\Types\TypeUpcE;
use AgeekDev\Barcode\Types\TypeUpcExtension2;
use AgeekDev\Barcode\Types\TypeUpcExtension5;
/**
* @deprecated see BarcodeType
*/
enum Type: string
{
case TYPE_CODE_32 = 'C32';
case TYPE_CODE_39 = 'C39';
case TYPE_CODE_39_CHECKSUM = 'C39+';
case TYPE_CODE_39E = 'C39E'; // CODE 39 EXTENDED
case TYPE_CODE_39E_CHECKSUM = 'C39E+'; // CODE 39 EXTENDED + CHECKSUM
case TYPE_CODE_93 = 'C93';
case TYPE_STANDARD_2_5 = 'S25';
case TYPE_STANDARD_2_5_CHECKSUM = 'S25+';
case TYPE_INTERLEAVED_2_5 = 'I25';
case TYPE_INTERLEAVED_2_5_CHECKSUM = 'I25+';
case TYPE_CODE_128 = 'C128';
case TYPE_CODE_128_A = 'C128A';
case TYPE_CODE_128_B = 'C128B';
case TYPE_CODE_128_C = 'C128C';
case TYPE_EAN_2 = 'EAN2'; // 2-Digits UPC-Based Extention
case TYPE_EAN_5 = 'EAN5'; // 5-Digits UPC-Based Extention
case TYPE_EAN_8 = 'EAN8';
case TYPE_EAN_13 = 'EAN13';
case TYPE_UPC_A = 'UPCA';
case TYPE_UPC_E = 'UPCE';
case TYPE_MSI = 'MSI'; // MSI (Variation of Plessey code)
case TYPE_MSI_CHECKSUM = 'MSI+'; // MSI + CHECKSUM (modulo 11)
case TYPE_POSTNET = 'POSTNET';
case TYPE_PLANET = 'PLANET';
case TYPE_RMS4CC = 'RMS4CC'; // RMS4CC (Royal Mail 4-state Customer Code) - CBC (Customer Bar Code)
case TYPE_KIX = 'KIX'; // KIX (Klant index - Customer index)
case TYPE_IMB = 'IMB'; // IMB - Intelligent Mail Barcode - Onecode - USPS-B-3200
case TYPE_CODABAR = 'CODABAR';
case TYPE_CODE_11 = 'CODE11';
case TYPE_PHARMA_CODE = 'PHARMA';
case TYPE_PHARMA_CODE_TWO_TRACKS = 'PHARMA2T';
public function class(): TypeInterface
{
return match ($this) {
self::TYPE_CODE_32 => new TypeCode32,
self::TYPE_CODE_39 => new TypeCode39,
self::TYPE_CODE_39_CHECKSUM => new TypeCode39Checksum,
self::TYPE_CODE_39E => new TypeCode39Extended,
self::TYPE_CODE_39E_CHECKSUM => new TypeCode39ExtendedChecksum,
self::TYPE_CODE_93 => new TypeCode93,
self::TYPE_STANDARD_2_5 => new TypeStandard2of5,
self::TYPE_STANDARD_2_5_CHECKSUM => new TypeStandard2of5Checksum,
self::TYPE_INTERLEAVED_2_5 => new TypeInterleaved25,
self::TYPE_INTERLEAVED_2_5_CHECKSUM => new TypeInterleaved25Checksum,
self::TYPE_CODE_128 => new TypeCode128,
self::TYPE_CODE_128_A => new TypeCode128A,
self::TYPE_CODE_128_B => new TypeCode128B,
self::TYPE_CODE_128_C => new TypeCode128C,
self::TYPE_EAN_2 => new TypeUpcExtension2,
self::TYPE_EAN_5 => new TypeUpcExtension5,
self::TYPE_EAN_8 => new TypeEan8,
self::TYPE_EAN_13 => new TypeEan13,
self::TYPE_UPC_A => new TypeUpcA,
self::TYPE_UPC_E => new TypeUpcE,
self::TYPE_MSI => new TypeMsi,
self::TYPE_MSI_CHECKSUM => new TypeMsiChecksum,
self::TYPE_POSTNET => new TypePostnet,
self::TYPE_PLANET => new TypePlanet,
self::TYPE_RMS4CC => new TypeRms4cc,
self::TYPE_KIX => new TypeKix,
self::TYPE_IMB => new TypeIntelligentMailBarcode,
self::TYPE_CODABAR => new TypeCodabar,
self::TYPE_CODE_11 => new TypeCode11,
self::TYPE_PHARMA_CODE => new TypePharmacode,
self::TYPE_PHARMA_CODE_TWO_TRACKS => new TypePharmacodeTwoCode,
};
}
}

View File

@@ -0,0 +1,5 @@
<?php
namespace AgeekDev\Barcode\Exceptions;
class BarcodeException extends \Exception {}

View File

@@ -0,0 +1,5 @@
<?php
namespace AgeekDev\Barcode\Exceptions;
class InvalidCharacterException extends BarcodeException {}

View File

@@ -0,0 +1,5 @@
<?php
namespace AgeekDev\Barcode\Exceptions;
class InvalidCheckDigitException extends BarcodeException {}

View File

@@ -0,0 +1,5 @@
<?php
namespace AgeekDev\Barcode\Exceptions;
class InvalidFormatException extends BarcodeException {}

View File

@@ -0,0 +1,5 @@
<?php
namespace AgeekDev\Barcode\Exceptions;
class InvalidLengthException extends BarcodeException {}

View File

@@ -0,0 +1,5 @@
<?php
namespace AgeekDev\Barcode\Exceptions;
class UnknownTypeException extends BarcodeException {}

View File

@@ -0,0 +1,28 @@
<?php
namespace AgeekDev\Barcode\Facades;
use AgeekDev\Barcode\Contracts\ImageType;
use AgeekDev\Barcode\Enums\BarcodeType;
use AgeekDev\Barcode\Enums\Type;
use Illuminate\Support\Facades\Facade;
/**
* @method static ImageType foregroundColor(string $foregroundColor)
* @method static ImageType height(int $height)
* @method static ImageType widthFactor(int $widthFactor)
* @method static ImageType type(BarcodeType|Type $type)
* @method static ImageType imageType(?string $driver)
* @method static ImageType useImagick()
* @method static ImageType useGd()
* @method static string generate(string $text)
*
* @see \AgeekDev\Barcode\BarcodeManager
*/
class Barcode extends Facade
{
protected static function getFacadeAccessor(): string
{
return 'laravel-barcode';
}
}

View File

@@ -0,0 +1,25 @@
<?php
namespace AgeekDev\Barcode\Helpers;
class BarcodeHelper
{
public static function getChecksum(string $code): int
{
$len = strlen($code);
$sum = 0;
for ($i = 0; $i < $len; $i += 2) {
$sum .= $code[$i];
}
$sum *= 3;
for ($i = 1; $i < $len; $i += 2) {
$sum .= ($code[$i]);
}
$r = $sum % 10;
if ($r > 0) {
$r = (10 - $r);
}
return $r;
}
}

View File

@@ -0,0 +1,40 @@
<?php
namespace AgeekDev\Barcode\Helpers;
use AgeekDev\Barcode\Barcode;
use AgeekDev\Barcode\BarcodeBar;
/**
* Convert binary barcode sequence string to Barcode representation.
*/
class BinarySequenceConverter
{
public static function convert(string $code, string $sequence): Barcode
{
$barcode = new Barcode($code);
return self::generate($sequence, $barcode);
}
public static function generate(string $sequence, Barcode $barcode): Barcode
{
$len = strlen($sequence);
$barWidth = 0;
for ($i = 0; $i < $len; $i++) {
$barWidth++;
if (($i === ($len - 1)) || (($i < ($len - 1)) && ($sequence[$i] !== $sequence[($i + 1)]))) {
if ($sequence[$i] === '1') {
$drawBar = true;
} else {
$drawBar = false;
}
$barcode->addBar(new BarcodeBar($barWidth, 1, $drawBar));
$barWidth = 0;
}
}
return $barcode;
}
}

View File

@@ -0,0 +1,67 @@
<?php
namespace AgeekDev\Barcode\Types;
use AgeekDev\Barcode\Barcode;
use AgeekDev\Barcode\BarcodeBar;
use AgeekDev\Barcode\Exceptions\InvalidCharacterException;
/*
* CODABAR barcodes.
* Older code often used in library systems, sometimes in blood banks
*/
class TypeCodabar implements TypeInterface
{
protected array $conversionTable = [
'0' => '11111221',
'1' => '11112211',
'2' => '11121121',
'3' => '22111111',
'4' => '11211211',
'5' => '21111211',
'6' => '12111121',
'7' => '12112111',
'8' => '12211111',
'9' => '21121111',
'-' => '11122111',
'$' => '11221111',
':' => '21112121',
'/' => '21211121',
'.' => '21212111',
'+' => '11222221',
'A' => '11221211',
'B' => '12121121',
'C' => '11121221',
'D' => '11122211',
];
/**
* @throws InvalidCharacterException
*/
public function getBarcodeData(string $code): Barcode
{
$barcode = new Barcode($code);
$code = 'A'.strtoupper($code).'A';
for ($i = 0, $iMax = strlen($code); $i < $iMax; $i++) {
if (! isset($this->conversionTable[$code[$i]])) {
throw new InvalidCharacterException('Char '.$code[$i].' is unsupported');
}
$seq = $this->conversionTable[$code[$i]];
for ($j = 0; $j < 8; $j++) {
if (($j % 2) === 0) {
$drawBar = true;
} else {
$drawBar = false;
}
$barWidth = $seq[$j];
$barcode->addBar(new BarcodeBar($barWidth, 1, $drawBar));
}
}
return $barcode;
}
}

View File

@@ -0,0 +1,114 @@
<?php
namespace AgeekDev\Barcode\Types;
use AgeekDev\Barcode\Barcode;
use AgeekDev\Barcode\BarcodeBar;
use AgeekDev\Barcode\Exceptions\InvalidCharacterException;
/*
* CODE11 barcodes.
* Used primarily for labeling telecommunications equipment
*/
class TypeCode11 implements TypeInterface
{
protected array $conversionTable = [
'0' => '111121',
'1' => '211121',
'2' => '121121',
'3' => '221111',
'4' => '112121',
'5' => '212111',
'6' => '122111',
'7' => '111221',
'8' => '211211',
'9' => '211111',
'-' => '112111',
'S' => '112211',
];
/**
* @throws InvalidCharacterException
*/
public function getBarcodeData(string $code): Barcode
{
$barcode = new Barcode($code);
$code .= $this->getCheckDigitC($code);
$code .= $this->getCheckDigitK($code);
$code = 'S'.$code.'S';
for ($i = 0, $iMax = strlen($code); $i < $iMax; $i++) {
if (! isset($this->conversionTable[$code[$i]])) {
throw new InvalidCharacterException('Char '.$code[$i].' is unsupported');
}
$seq = $this->conversionTable[$code[$i]];
for ($j = 0, $jMax = strlen($seq); $j < $jMax; $j++) {
if (($j % 2) === 0) {
$drawBar = true;
} else {
$drawBar = false;
}
$barWidth = $seq[$j];
$barcode->addBar(new BarcodeBar($barWidth, 1, $drawBar));
}
}
return $barcode;
}
private function getCheckDigitC(string $code): string
{
$p = 1;
$check = 0;
for ($i = (strlen($code) - 1); $i >= 0; $i--) {
$digit = $code[$i];
if ($digit === '-') {
$dval = 10;
} else {
$dval = (int) $digit;
}
$check += ($dval * $p);
$p++;
if ($p > 10) {
$p = 1;
}
}
$check %= 11;
if ($check === 10) {
$check = '-';
}
return $check;
}
private function getCheckDigitK(string $code): string
{
if (strlen($code) <= 10) {
return '';
}
$p = 1;
$check = 0;
for ($i = (strlen($code) - 1); $i >= 0; $i--) {
$digit = $code[$i];
if ($digit === '-') {
$dval = 10;
} else {
$dval = (int) $digit;
}
$check += ($dval * $p);
$p++;
if ($p > 9) {
$p = 1;
}
}
$check %= 11;
return (string) $check;
}
}

View File

@@ -0,0 +1,415 @@
<?php
namespace AgeekDev\Barcode\Types;
use AgeekDev\Barcode\Barcode;
use AgeekDev\Barcode\BarcodeBar;
use AgeekDev\Barcode\Exceptions\InvalidCharacterException;
use AgeekDev\Barcode\Exceptions\InvalidLengthException;
/*
* C128 barcodes.
* Very capable code, excellent density, high reliability; in very wide use world-wide
*
* @param $code (string) code to represent.
* @param $type (string) barcode type: A, B, C or empty for automatic switch (AUTO mode)
*/
class TypeCode128 implements TypeInterface
{
protected ?string $type = null;
protected array $conversionTable = [
'212222', /* 00 */
'222122', /* 01 */
'222221', /* 02 */
'121223', /* 03 */
'121322', /* 04 */
'131222', /* 05 */
'122213', /* 06 */
'122312', /* 07 */
'132212', /* 08 */
'221213', /* 09 */
'221312', /* 10 */
'231212', /* 11 */
'112232', /* 12 */
'122132', /* 13 */
'122231', /* 14 */
'113222', /* 15 */
'123122', /* 16 */
'123221', /* 17 */
'223211', /* 18 */
'221132', /* 19 */
'221231', /* 20 */
'213212', /* 21 */
'223112', /* 22 */
'312131', /* 23 */
'311222', /* 24 */
'321122', /* 25 */
'321221', /* 26 */
'312212', /* 27 */
'322112', /* 28 */
'322211', /* 29 */
'212123', /* 30 */
'212321', /* 31 */
'232121', /* 32 */
'111323', /* 33 */
'131123', /* 34 */
'131321', /* 35 */
'112313', /* 36 */
'132113', /* 37 */
'132311', /* 38 */
'211313', /* 39 */
'231113', /* 40 */
'231311', /* 41 */
'112133', /* 42 */
'112331', /* 43 */
'132131', /* 44 */
'113123', /* 45 */
'113321', /* 46 */
'133121', /* 47 */
'313121', /* 48 */
'211331', /* 49 */
'231131', /* 50 */
'213113', /* 51 */
'213311', /* 52 */
'213131', /* 53 */
'311123', /* 54 */
'311321', /* 55 */
'331121', /* 56 */
'312113', /* 57 */
'312311', /* 58 */
'332111', /* 59 */
'314111', /* 60 */
'221411', /* 61 */
'431111', /* 62 */
'111224', /* 63 */
'111422', /* 64 */
'121124', /* 65 */
'121421', /* 66 */
'141122', /* 67 */
'141221', /* 68 */
'112214', /* 69 */
'112412', /* 70 */
'122114', /* 71 */
'122411', /* 72 */
'142112', /* 73 */
'142211', /* 74 */
'241211', /* 75 */
'221114', /* 76 */
'413111', /* 77 */
'241112', /* 78 */
'134111', /* 79 */
'111242', /* 80 */
'121142', /* 81 */
'121241', /* 82 */
'114212', /* 83 */
'124112', /* 84 */
'124211', /* 85 */
'411212', /* 86 */
'421112', /* 87 */
'421211', /* 88 */
'212141', /* 89 */
'214121', /* 90 */
'412121', /* 91 */
'111143', /* 92 */
'111341', /* 93 */
'131141', /* 94 */
'114113', /* 95 */
'114311', /* 96 */
'411113', /* 97 */
'411311', /* 98 */
'113141', /* 99 */
'114131', /* 100 */
'311141', /* 101 */
'411131', /* 102 */
'211412', /* 103 START A */
'211214', /* 104 START B */
'211232', /* 105 START C */
'233111', /* STOP */
'200000', /* END */
];
/**
* @throws InvalidCharacterException
* @throws InvalidLengthException
*/
public function getBarcodeData(string $code): Barcode
{
if (trim($code) === '') {
throw new InvalidLengthException('You should provide a barcode string.');
}
// ASCII characters for code A (ASCII 00 - 95)
$keys_a = ' !"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_';
$keys_a .= chr(0).chr(1).chr(2).chr(3).chr(4).chr(5).chr(6).chr(7).chr(8).chr(9);
$keys_a .= chr(10).chr(11).chr(12).chr(13).chr(14).chr(15).chr(16).chr(17).chr(18).chr(19);
$keys_a .= chr(20).chr(21).chr(22).chr(23).chr(24).chr(25).chr(26).chr(27).chr(28).chr(29);
$keys_a .= chr(30).chr(31);
// ASCII characters for code B (ASCII 32 - 127)
$keys_b = ' !"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~'.chr(127);
// special codes
$fnc_a = [241 => 102, 242 => 97, 243 => 96, 244 => 101];
$fnc_b = [241 => 102, 242 => 97, 243 => 96, 244 => 100];
// array of symbols
$code_data = [];
// length of the code
$len = strlen($code);
switch (strtoupper($this->type ?? '')) {
case 'A':
$startid = 103;
for ($i = 0; $i < $len; $i++) {
$char = $code[$i];
$char_id = ord($char);
if (($char_id <= 244) && ($char_id >= 241)) {
$code_data[] = $fnc_a[$char_id];
} elseif ($char_id <= 95) {
$code_data[] = strpos($keys_a, $char);
} else {
throw new InvalidCharacterException('Char '.$char.' is unsupported');
}
}
break;
case 'B':
$startid = 104;
for ($i = 0; $i < $len; $i++) {
$char = $code[$i];
$char_id = ord($char);
if (($char_id >= 241) && ($char_id <= 244)) {
$code_data[] = $fnc_b[$char_id];
} elseif (($char_id >= 32) && ($char_id <= 127)) {
$code_data[] = strpos($keys_b, $char);
} else {
throw new InvalidCharacterException('Char '.$char.' is unsupported');
}
}
break;
case 'C':
$startid = 105;
if (ord($code[0]) === 241) {
$code_data[] = 102;
$code = substr($code, 1);
$len--;
}
if (($len % 2) !== 0) {
throw new InvalidLengthException('Length must be even');
}
for ($i = 0; $i < $len; $i += 2) {
$chrnum = $code[$i].$code[$i + 1];
if (preg_match('/(\d{2})/', $chrnum) > 0) {
$code_data[] = (int) $chrnum;
} else {
throw new InvalidCharacterException;
}
}
break;
default:
// split code into sequences
$sequence = [];
// get numeric sequences (if any)
$numseq = [];
preg_match_all('/(\d{4,})/', $code, $numseq, PREG_OFFSET_CAPTURE);
if (! empty($numseq[1])) {
$end_offset = 0;
foreach ($numseq[1] as $val) {
$offset = $val[1];
// numeric sequence
$slen = strlen($val[0]);
if (($slen % 2) != 0) {
// the length must be even
$offset++;
$val[0] = substr($val[0], 1);
}
if ($offset > $end_offset) {
// non numeric sequence
$sequence = array_merge($sequence, $this->get128ABsequence(substr($code, $end_offset, ($offset - $end_offset))));
}
// numeric sequence fallback
$slen = strlen($val[0]);
if (($slen % 2) != 0) {
// the length must be even
$slen--;
}
$sequence[] = ['C', substr($code, $offset, $slen), $slen];
$end_offset = $offset + $slen;
}
if ($end_offset < $len) {
$sequence = array_merge($sequence, $this->get128ABsequence(substr($code, $end_offset)));
}
} else {
// text code (non C mode)
$sequence = array_merge($sequence, $this->get128ABsequence($code));
}
$startid = 0;
// process the sequence
foreach ($sequence as $key => $seq) {
switch ($seq[0]) {
case 'A':
if ($key === 0) {
$startid = 103;
} elseif ($sequence[($key - 1)][0] !== 'A') {
if (($seq[2] === 1) && ($key > 0) && ($sequence[($key - 1)][0] === 'B') && (! isset($sequence[($key - 1)][3]))) {
// single character shift
$code_data[] = 98;
// mark shift
$sequence[$key][3] = true;
} elseif (! isset($sequence[($key - 1)][3])) {
$code_data[] = 101;
}
}
for ($i = 0; $i < $seq[2]; $i++) {
$char = $seq[1][$i];
$char_id = ord($char);
if (($char_id >= 241) && ($char_id <= 244)) {
$code_data[] = $fnc_a[$char_id];
} else {
$code_data[] = strpos($keys_a, $char);
}
}
break;
case 'B':
if ($key === 0) {
$tmpchr = ord($seq[1][0]);
if (($seq[2] === 1) && ($tmpchr >= 241) && ($tmpchr <= 244) && isset($sequence[($key + 1)]) && ($sequence[($key + 1)][0] !== 'B')) {
switch ($sequence[($key + 1)][0]) {
case 'A':
$startid = 103;
$sequence[$key][0] = 'A';
$code_data[] = $fnc_a[$tmpchr];
break;
case 'C':
$startid = 105;
$sequence[$key][0] = 'C';
$code_data[] = $fnc_a[$tmpchr];
break;
}
break;
}
$startid = 104;
} elseif ($sequence[($key - 1)][0] !== 'B') {
if (($seq[2] === 1) && ($key > 0) && ($sequence[($key - 1)][0] === 'A') && (! isset($sequence[($key - 1)][3]))) {
// single character shift
$code_data[] = 98;
// mark shift
$sequence[$key][3] = true;
} elseif (! isset($sequence[($key - 1)][3])) {
$code_data[] = 100;
}
}
for ($i = 0; $i < $seq[2]; $i++) {
$char = $seq[1][$i];
$char_id = ord($char);
if (($char_id >= 241) && ($char_id <= 244)) {
$code_data[] = $fnc_b[$char_id];
} else {
$code_data[] = strpos($keys_b, $char);
}
}
break;
case 'C':
if ($key === 0) {
$startid = 105;
} elseif ($sequence[($key - 1)][0] !== 'C') {
$code_data[] = 99;
}
for ($i = 0; $i < $seq[2]; $i += 2) {
$chrnum = $seq[1][$i].$seq[1][$i + 1];
$code_data[] = (int) $chrnum;
}
break;
}
}
}
// calculate check character
$sum = $startid;
foreach ($code_data as $key => $val) {
$sum += ($val * ($key + 1));
}
// add check character
$code_data[] = ($sum % 103);
// add stop sequence
$code_data[] = 106;
$code_data[] = 107;
// add start code at the beginning
array_unshift($code_data, $startid);
// build barcode array
$barcode = new Barcode($code);
foreach ($code_data as $val) {
$seq = $this->conversionTable[$val];
for ($j = 0; $j < 6; $j++) {
if (($j % 2) === 0) {
$t = true; // bar
} else {
$t = false; // space
}
$w = $seq[$j];
$barcode->addBar(new BarcodeBar($w, 1, $t));
}
}
return $barcode;
}
/**
* Split text code in A/B sequence for 128 code
*
* @param $code (string) code to split.
*/
protected function get128ABsequence(string $code): array
{
$len = strlen($code);
$sequence = [];
// get A sequences (if any)
$numseq = [];
preg_match_all('/([\x00-\x1f])/', $code, $numseq, PREG_OFFSET_CAPTURE);
if (! empty($numseq[1])) {
$end_offset = 0;
foreach ($numseq[1] as $val) {
$offset = $val[1];
if ($offset > $end_offset) {
// B sequence
$sequence[] = [
'B',
substr($code, $end_offset, ($offset - $end_offset)),
($offset - $end_offset),
];
}
// A sequence
$slen = strlen($val[0]);
$sequence[] = ['A', substr($code, $offset, $slen), $slen];
$end_offset = $offset + $slen;
}
if ($end_offset < $len) {
$sequence[] = ['B', substr($code, $end_offset), ($len - $end_offset)];
}
} else {
// only B sequence
$sequence[] = ['B', $code, $len];
}
return $sequence;
}
}

View File

@@ -0,0 +1,16 @@
<?php
namespace AgeekDev\Barcode\Types;
/*
* C128 barcodes.
* Very capable code, excellent density, high reliability; in very wide use world-wide
*
* @param $code (string) code to represent.
* @param $type (string) barcode type: A, B, C or empty for automatic switch (AUTO mode)
*/
class TypeCode128A extends TypeCode128
{
protected ?string $type = 'A';
}

View File

@@ -0,0 +1,16 @@
<?php
namespace AgeekDev\Barcode\Types;
/*
* C128 barcodes.
* Very capable code, excellent density, high reliability; in very wide use world-wide
*
* @param $code (string) code to represent.
* @param $type (string) barcode type: A, B, C or empty for automatic switch (AUTO mode)
*/
class TypeCode128B extends TypeCode128
{
protected ?string $type = 'B';
}

View File

@@ -0,0 +1,16 @@
<?php
namespace AgeekDev\Barcode\Types;
/*
* C128 barcodes.
* Very capable code, excellent density, high reliability; in very wide use world-wide
*
* @param $code (string) code to represent.
* @param $type (string) barcode type: A, B, C or empty for automatic switch (AUTO mode)
*/
class TypeCode128C extends TypeCode128
{
protected ?string $type = 'C';
}

View File

@@ -0,0 +1,60 @@
<?php
namespace AgeekDev\Barcode\Types;
use AgeekDev\Barcode\Barcode;
/*
* CODE 32 - italian pharmaceutical
* General-purpose code in very wide use world-wide
*/
class TypeCode32 extends TypeCode39
{
protected array $conversionTable32 = [
'0' => '0',
'1' => '1',
'2' => '2',
'3' => '3',
'4' => '4',
'5' => '5',
'6' => '6',
'7' => '7',
'8' => '8',
'9' => '9',
'10' => 'B',
'11' => 'C',
'12' => 'D',
'13' => 'F',
'14' => 'G',
'15' => 'H',
'16' => 'J',
'17' => 'K',
'18' => 'L',
'19' => 'M',
'20' => 'N',
'21' => 'P',
'22' => 'Q',
'23' => 'R',
'24' => 'S',
'25' => 'T',
'26' => 'U',
'27' => 'V',
'28' => 'W',
'29' => 'X',
'30' => 'Y',
'31' => 'Z',
];
public function getBarcodeData(string $code): Barcode
{
$code39 = '';
$codeElab = $code;
for ($e = 5; $e >= 0; $e--) {
$code39 .= $this->conversionTable32[(int) ($codeElab / (32 ** $e))];
$codeElab %= 32 ** $e;
}
return parent::getBarcodeData($code39);
}
}

View File

@@ -0,0 +1,330 @@
<?php
namespace AgeekDev\Barcode\Types;
use AgeekDev\Barcode\Barcode;
use AgeekDev\Barcode\BarcodeBar;
use AgeekDev\Barcode\Exceptions\InvalidCharacterException;
use AgeekDev\Barcode\Exceptions\InvalidLengthException;
/*
* CODE 39 - ANSI MH10.8M-1983 - USD-3 - 3 of 9.
* General-purpose code in very wide use world-wide
*/
class TypeCode39 implements TypeInterface
{
protected bool $extended = false;
protected bool $checksum = false;
protected array $conversionTable = [
'0' => '111331311',
'1' => '311311113',
'2' => '113311113',
'3' => '313311111',
'4' => '111331113',
'5' => '311331111',
'6' => '113331111',
'7' => '111311313',
'8' => '311311311',
'9' => '113311311',
'A' => '311113113',
'B' => '113113113',
'C' => '313113111',
'D' => '111133113',
'E' => '311133111',
'F' => '113133111',
'G' => '111113313',
'H' => '311113311',
'I' => '113113311',
'J' => '111133311',
'K' => '311111133',
'L' => '113111133',
'M' => '313111131',
'N' => '111131133',
'O' => '311131131',
'P' => '113131131',
'Q' => '111111333',
'R' => '311111331',
'S' => '113111331',
'T' => '111131331',
'U' => '331111113',
'V' => '133111113',
'W' => '333111111',
'X' => '131131113',
'Y' => '331131111',
'Z' => '133131111',
'-' => '131111313',
'.' => '331111311',
' ' => '133111311',
'$' => '131313111',
'/' => '131311131',
'+' => '131113131',
'%' => '111313131',
'*' => '131131311',
];
/**
* @throws InvalidCharacterException
* @throws InvalidLengthException
*/
public function getBarcodeData(string $code): Barcode
{
if (trim($code) === '') {
throw new InvalidLengthException('You should provide a barcode string.');
}
if ($this->extended) {
// extended mode
$code = $this->encode_code39_ext($code);
}
if ($this->checksum) {
// checksum
$code .= $this->checksum_code39($code);
}
// add start and stop codes
$code = '*'.$code.'*';
$barcode = new Barcode($code);
for ($i = 0, $iMax = strlen($code); $i < $iMax; $i++) {
$char = $code[$i];
if (! isset($this->conversionTable[$char])) {
throw new InvalidCharacterException('Char '.$char.' is unsupported');
}
for ($j = 0; $j < 9; $j++) {
if (($j % 2) === 0) {
$drawBar = true;
} else {
$drawBar = false;
}
$barWidth = $this->conversionTable[$char][$j];
$barcode->addBar(new BarcodeBar($barWidth, 1, $drawBar));
}
// inter character gap
$barcode->addBar(new BarcodeBar(1, 1, false));
}
return $barcode;
}
/**
* Encode a string to be used for CODE 39 Extended mode.
*
* @param string $code code to represent.
*
* @throws InvalidCharacterException
*/
protected function encode_code39_ext(string $code): string
{
$encode = [
chr(0) => '%U',
chr(1) => '$A',
chr(2) => '$B',
chr(3) => '$C',
chr(4) => '$D',
chr(5) => '$E',
chr(6) => '$F',
chr(7) => '$G',
chr(8) => '$H',
chr(9) => '$I',
chr(10) => '$J',
chr(11) => '$K',
chr(12) => '$L',
chr(13) => '$M',
chr(14) => '$N',
chr(15) => '$O',
chr(16) => '$P',
chr(17) => '$Q',
chr(18) => '$R',
chr(19) => '$S',
chr(20) => '$T',
chr(21) => '$U',
chr(22) => '$V',
chr(23) => '$W',
chr(24) => '$X',
chr(25) => '$Y',
chr(26) => '$Z',
chr(27) => '%A',
chr(28) => '%B',
chr(29) => '%C',
chr(30) => '%D',
chr(31) => '%E',
chr(32) => ' ',
chr(33) => '/A',
chr(34) => '/B',
chr(35) => '/C',
chr(36) => '/D',
chr(37) => '/E',
chr(38) => '/F',
chr(39) => '/G',
chr(40) => '/H',
chr(41) => '/I',
chr(42) => '/J',
chr(43) => '/K',
chr(44) => '/L',
chr(45) => '-',
chr(46) => '.',
chr(47) => '/O',
chr(48) => '0',
chr(49) => '1',
chr(50) => '2',
chr(51) => '3',
chr(52) => '4',
chr(53) => '5',
chr(54) => '6',
chr(55) => '7',
chr(56) => '8',
chr(57) => '9',
chr(58) => '/Z',
chr(59) => '%F',
chr(60) => '%G',
chr(61) => '%H',
chr(62) => '%I',
chr(63) => '%J',
chr(64) => '%V',
chr(65) => 'A',
chr(66) => 'B',
chr(67) => 'C',
chr(68) => 'D',
chr(69) => 'E',
chr(70) => 'F',
chr(71) => 'G',
chr(72) => 'H',
chr(73) => 'I',
chr(74) => 'J',
chr(75) => 'K',
chr(76) => 'L',
chr(77) => 'M',
chr(78) => 'N',
chr(79) => 'O',
chr(80) => 'P',
chr(81) => 'Q',
chr(82) => 'R',
chr(83) => 'S',
chr(84) => 'T',
chr(85) => 'U',
chr(86) => 'V',
chr(87) => 'W',
chr(88) => 'X',
chr(89) => 'Y',
chr(90) => 'Z',
chr(91) => '%K',
chr(92) => '%L',
chr(93) => '%M',
chr(94) => '%N',
chr(95) => '%O',
chr(96) => '%W',
chr(97) => '+A',
chr(98) => '+B',
chr(99) => '+C',
chr(100) => '+D',
chr(101) => '+E',
chr(102) => '+F',
chr(103) => '+G',
chr(104) => '+H',
chr(105) => '+I',
chr(106) => '+J',
chr(107) => '+K',
chr(108) => '+L',
chr(109) => '+M',
chr(110) => '+N',
chr(111) => '+O',
chr(112) => '+P',
chr(113) => '+Q',
chr(114) => '+R',
chr(115) => '+S',
chr(116) => '+T',
chr(117) => '+U',
chr(118) => '+V',
chr(119) => '+W',
chr(120) => '+X',
chr(121) => '+Y',
chr(122) => '+Z',
chr(123) => '%P',
chr(124) => '%Q',
chr(125) => '%R',
chr(126) => '%S',
chr(127) => '%T',
];
$code_ext = '';
for ($i = 0, $iMax = strlen($code); $i < $iMax; $i++) {
if (ord($code[$i]) > 127) {
throw new InvalidCharacterException('Only supports till char 127');
}
$code_ext .= $encode[$code[$i]];
}
return $code_ext;
}
/**
* Calculate CODE 39 checksum (modulo 43).
*
* @param string $code code to represent.
*/
protected function checksum_code39(string $code): string
{
$chars = [
'0',
'1',
'2',
'3',
'4',
'5',
'6',
'7',
'8',
'9',
'A',
'B',
'C',
'D',
'E',
'F',
'G',
'H',
'I',
'J',
'K',
'L',
'M',
'N',
'O',
'P',
'Q',
'R',
'S',
'T',
'U',
'V',
'W',
'X',
'Y',
'Z',
'-',
'.',
' ',
'$',
'/',
'+',
'%',
];
$sum = 0;
for ($i = 0, $iMax = strlen($code); $i < $iMax; $i++) {
$k = array_keys($chars, $code[$i]);
$sum += $k[0];
}
$j = ($sum % 43);
return $chars[$j];
}
}

View File

@@ -0,0 +1,15 @@
<?php
namespace AgeekDev\Barcode\Types;
/*
* CODE 39 - ANSI MH10.8M-1983 - USD-3 - 3 of 9.
* General-purpose code in very wide use world-wide
*/
class TypeCode39Checksum extends TypeCode39
{
protected bool $extended = false;
protected bool $checksum = true;
}

View File

@@ -0,0 +1,15 @@
<?php
namespace AgeekDev\Barcode\Types;
/*
* CODE 39 - ANSI MH10.8M-1983 - USD-3 - 3 of 9.
* General-purpose code in very wide use world-wide
*/
class TypeCode39Extended extends TypeCode39
{
protected bool $extended = true;
protected bool $checksum = false;
}

View File

@@ -0,0 +1,15 @@
<?php
namespace AgeekDev\Barcode\Types;
/*
* CODE 39 - ANSI MH10.8M-1983 - USD-3 - 3 of 9.
* General-purpose code in very wide use world-wide
*/
class TypeCode39ExtendedChecksum extends TypeCode39
{
protected bool $extended = true;
protected bool $checksum = true;
}

View File

@@ -0,0 +1,286 @@
<?php
namespace AgeekDev\Barcode\Types;
use AgeekDev\Barcode\Barcode;
use AgeekDev\Barcode\BarcodeBar;
use AgeekDev\Barcode\Exceptions\InvalidCharacterException;
/*
* CODE 93 - USS-93
* Compact code similar to Code 39
*
* reference: https://en.wikipedia.org/wiki/Code_93#Full_ASCII_Code_93
*/
class TypeCode93 implements TypeInterface
{
protected array $conversionTable = [
48 => '131112', // 0
49 => '111213', // 1
50 => '111312', // 2
51 => '111411', // 3
52 => '121113', // 4
53 => '121212', // 5
54 => '121311', // 6
55 => '111114', // 7
56 => '131211', // 8
57 => '141111', // 9
65 => '211113', // A
66 => '211212', // B
67 => '211311', // C
68 => '221112', // D
69 => '221211', // E
70 => '231111', // F
71 => '112113', // G
72 => '112212', // H
73 => '112311', // I
74 => '122112', // J
75 => '132111', // K
76 => '111123', // L
77 => '111222', // M
78 => '111321', // N
79 => '121122', // O
80 => '131121', // P
81 => '212112', // Q
82 => '212211', // R
83 => '211122', // S
84 => '211221', // T
85 => '221121', // U
86 => '222111', // V
87 => '112122', // W
88 => '112221', // X
89 => '122121', // Y
90 => '123111', // Z
45 => '121131', // -
46 => '311112', // .
32 => '311211', //
36 => '321111', // $
47 => '112131', // /
43 => '113121', // +
37 => '211131', // %
97 => '121221', // ($)
98 => '312111', // (%)
99 => '311121', // (/)
100 => '122211', // (+)
42 => '111141', // start-stop
];
/**
* @throws InvalidCharacterException
*/
public function getBarcodeData(string $code): Barcode
{
$encode = [
chr(0) => 'bU',
chr(1) => 'aA',
chr(2) => 'aB',
chr(3) => 'aC',
chr(4) => 'aD',
chr(5) => 'aE',
chr(6) => 'aF',
chr(7) => 'aG',
chr(8) => 'aH',
chr(9) => 'aI',
chr(10) => 'aJ',
chr(11) => 'aK',
chr(12) => 'aL',
chr(13) => 'aM',
chr(14) => 'aN',
chr(15) => 'aO',
chr(16) => 'aP',
chr(17) => 'aQ',
chr(18) => 'aR',
chr(19) => 'aS',
chr(20) => 'aT',
chr(21) => 'aU',
chr(22) => 'aV',
chr(23) => 'aW',
chr(24) => 'aX',
chr(25) => 'aY',
chr(26) => 'aZ',
chr(27) => 'bA',
chr(28) => 'bB',
chr(29) => 'bC',
chr(30) => 'bD',
chr(31) => 'bE',
chr(32) => ' ',
chr(33) => 'cA',
chr(34) => 'cB',
chr(35) => 'cC',
chr(36) => '$',
chr(37) => '%',
chr(38) => 'cF',
chr(39) => 'cG',
chr(40) => 'cH',
chr(41) => 'cI',
chr(42) => 'cJ',
chr(43) => '+',
chr(44) => 'cL',
chr(45) => '-',
chr(46) => '.',
chr(47) => '/',
chr(48) => '0',
chr(49) => '1',
chr(50) => '2',
chr(51) => '3',
chr(52) => '4',
chr(53) => '5',
chr(54) => '6',
chr(55) => '7',
chr(56) => '8',
chr(57) => '9',
chr(58) => 'cZ',
chr(59) => 'bF',
chr(60) => 'bG',
chr(61) => 'bH',
chr(62) => 'bI',
chr(63) => 'bJ',
chr(64) => 'bV',
chr(65) => 'A',
chr(66) => 'B',
chr(67) => 'C',
chr(68) => 'D',
chr(69) => 'E',
chr(70) => 'F',
chr(71) => 'G',
chr(72) => 'H',
chr(73) => 'I',
chr(74) => 'J',
chr(75) => 'K',
chr(76) => 'L',
chr(77) => 'M',
chr(78) => 'N',
chr(79) => 'O',
chr(80) => 'P',
chr(81) => 'Q',
chr(82) => 'R',
chr(83) => 'S',
chr(84) => 'T',
chr(85) => 'U',
chr(86) => 'V',
chr(87) => 'W',
chr(88) => 'X',
chr(89) => 'Y',
chr(90) => 'Z',
chr(91) => 'bK',
chr(92) => 'bL',
chr(93) => 'bM',
chr(94) => 'bN',
chr(95) => 'bO',
chr(96) => 'bW',
chr(97) => 'dA',
chr(98) => 'dB',
chr(99) => 'dC',
chr(100) => 'dD',
chr(101) => 'dE',
chr(102) => 'dF',
chr(103) => 'dG',
chr(104) => 'dH',
chr(105) => 'dI',
chr(106) => 'dJ',
chr(107) => 'dK',
chr(108) => 'dL',
chr(109) => 'dM',
chr(110) => 'dN',
chr(111) => 'dO',
chr(112) => 'dP',
chr(113) => 'dQ',
chr(114) => 'dR',
chr(115) => 'dS',
chr(116) => 'dT',
chr(117) => 'dU',
chr(118) => 'dV',
chr(119) => 'dW',
chr(120) => 'dX',
chr(121) => 'dY',
chr(122) => 'dZ',
chr(123) => 'bP',
chr(124) => 'bQ',
chr(125) => 'bR',
chr(126) => 'bS',
chr(127) => 'bT',
];
$code_ext = '';
$clen = strlen($code);
for ($i = 0; $i < $clen; $i++) {
if (ord($code[$i]) > 127) {
throw new InvalidCharacterException('Only supports till char 127');
}
$code_ext .= $encode[$code[$i]];
}
// checksum
$code_ext .= $this->checksum_code93($code_ext);
// add start and stop codes
$code = '*'.$code_ext.'*';
$barcode = new Barcode($code);
for ($i = 0, $iMax = strlen($code); $i < $iMax; $i++) {
$char = ord($code[$i]);
if (! isset($this->conversionTable[$char])) {
throw new InvalidCharacterException('Char '.$char.' is unsupported');
}
for ($j = 0; $j < 6; $j++) {
if (($j % 2) === 0) {
$drawBar = true;
} else {
$drawBar = false;
}
$barWidth = $this->conversionTable[$char][$j];
$barcode->addBar(new BarcodeBar($barWidth, 1, $drawBar));
}
}
$barcode->addBar(new BarcodeBar(1, 1, true));
return $barcode;
}
/**
* Calculate CODE 93 checksum (modulo 47).
*
* @param $code (string) code to represent.
*/
protected function checksum_code93(string $code): string
{
$chars = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '-', '.', ' ', '$', '/', '+', '%', 'a', 'b', 'c', 'd'];
// calculate check digit C
$len = strlen($code);
$p = 1;
$check = 0;
for ($i = ($len - 1); $i >= 0; $i--) {
$k = array_keys($chars, $code[$i]);
$check += ($k[0] * $p);
$p++;
if ($p > 20) {
$p = 1;
}
}
$check %= 47;
$c = $chars[$check];
$code .= $c;
// calculate check digit K
$p = 1;
$check = 0;
for ($i = $len; $i >= 0; $i--) {
$k = array_keys($chars, $code[$i]);
$check += ($k[0] * $p);
$p++;
if ($p > 15) {
$p = 1;
}
}
$check %= 47;
$k = $chars[$check];
return $c.$k;
}
}

View File

@@ -0,0 +1,22 @@
<?php
namespace AgeekDev\Barcode\Types;
/*
* EAN13 and UPC-A barcodes.
* EAN13: European Article Numbering international retail product code
* UPC-A: Universal product code seen on almost all retail products in the USA and Canada
* UPC-E: Short version of UPC symbol
*
* @param $code (string) code to represent.
* @param $len (string) barcode type: 6 = UPC-E, 8 = EAN8, 13 = EAN13, 12 = UPC-A
*/
class TypeEan13 extends TypeEanUpcBase
{
protected int $length = 13;
protected bool $upca = false;
protected bool $upce = false;
}

View File

@@ -0,0 +1,22 @@
<?php
namespace AgeekDev\Barcode\Types;
/*
* EAN13 and UPC-A barcodes.
* EAN13: European Article Numbering international retail product code
* UPC-A: Universal product code seen on almost all retail products in the USA and Canada
* UPC-E: Short version of UPC symbol
*
* @param $code (string) code to represent.
* @param $len (string) barcode type: 6 = UPC-E, 8 = EAN8, 13 = EAN13, 12 = UPC-A
*/
class TypeEan8 extends TypeEanUpcBase
{
protected int $length = 8;
protected bool $upca = false;
protected bool $upce = false;
}

View File

@@ -0,0 +1,224 @@
<?php
namespace AgeekDev\Barcode\Types;
use AgeekDev\Barcode\Barcode;
use AgeekDev\Barcode\Exceptions\InvalidCharacterException;
use AgeekDev\Barcode\Exceptions\InvalidCheckDigitException;
use AgeekDev\Barcode\Exceptions\InvalidLengthException;
use AgeekDev\Barcode\Helpers\BinarySequenceConverter;
/*
* EAN13 and UPC-A barcodes.
* EAN13: European Article Numbering international retail product code
* UPC-A: Universal product code seen on almost all retail products in the USA and Canada
* UPC-E: Short version of UPC symbol
*
* @param $code (string) code to represent.
* @param $len (string) barcode type: 6 = UPC-E, 8 = EAN8, 13 = EAN13, 12 = UPC-A
*/
abstract class TypeEanUpcBase implements TypeInterface
{
protected int $length = 13;
protected bool $upca = false;
protected bool $upce = false;
/**
* @throws InvalidCheckDigitException
* @throws InvalidCharacterException
* @throws InvalidLengthException
*/
public function getBarcodeData(string $code): Barcode
{
if (trim($code) === '') {
throw new InvalidLengthException('You should provide a barcode string.');
}
$length = $this->length;
$dataLength = $length - 1;
// Add zero padding in front
$code = str_pad($code, $dataLength, '0', STR_PAD_LEFT);
$checksumDigit = $this->calculateChecksumDigit($code);
if (strlen($code) === $dataLength) {
$code .= $checksumDigit;
} elseif ($checksumDigit !== (int) $code[$dataLength]) {
// If length of given barcode is same as final length, barcode is including checksum
// Make sure that checksum is the same as we calculated
throw new InvalidCheckDigitException;
}
if ($this->upca || $this->upce) {
$code = '0'.$code;
$length++;
}
if ($this->upce) {
// convert UPC-A to UPC-E
$tmp = substr($code, 4, 3);
if (($tmp === '000') || ($tmp === '100') || ($tmp === '200')) {
// manufacturer code ends in 000, 100, or 200
$upce_code = substr($code, 2, 2).substr($code, 9, 3).$code[4];
} else {
$tmp = substr($code, 5, 2);
if ($tmp === '00') {
// manufacturer code ends in 00
$upce_code = substr($code, 2, 3).substr($code, 10, 2).'3';
} else {
$tmp = $code[6];
if ($tmp === '0') {
// manufacturer code ends in 0
$upce_code = substr($code, 2, 4).$code[11].'4';
} else {
// manufacturer code does not end in zero
$upce_code = substr($code, 2, 5).$code[11];
}
}
}
}
// Convert digits to bars
$codes = [
'A' => [ // left odd parity
'0' => '0001101',
'1' => '0011001',
'2' => '0010011',
'3' => '0111101',
'4' => '0100011',
'5' => '0110001',
'6' => '0101111',
'7' => '0111011',
'8' => '0110111',
'9' => '0001011',
],
'B' => [ // left even parity
'0' => '0100111',
'1' => '0110011',
'2' => '0011011',
'3' => '0100001',
'4' => '0011101',
'5' => '0111001',
'6' => '0000101',
'7' => '0010001',
'8' => '0001001',
'9' => '0010111',
],
'C' => [ // right
'0' => '1110010',
'1' => '1100110',
'2' => '1101100',
'3' => '1000010',
'4' => '1011100',
'5' => '1001110',
'6' => '1010000',
'7' => '1000100',
'8' => '1001000',
'9' => '1110100',
],
];
$parities = [
'0' => ['A', 'A', 'A', 'A', 'A', 'A'],
'1' => ['A', 'A', 'B', 'A', 'B', 'B'],
'2' => ['A', 'A', 'B', 'B', 'A', 'B'],
'3' => ['A', 'A', 'B', 'B', 'B', 'A'],
'4' => ['A', 'B', 'A', 'A', 'B', 'B'],
'5' => ['A', 'B', 'B', 'A', 'A', 'B'],
'6' => ['A', 'B', 'B', 'B', 'A', 'A'],
'7' => ['A', 'B', 'A', 'B', 'A', 'B'],
'8' => ['A', 'B', 'A', 'B', 'B', 'A'],
'9' => ['A', 'B', 'B', 'A', 'B', 'A'],
];
$upce_parities = [
[
'0' => ['B', 'B', 'B', 'A', 'A', 'A'],
'1' => ['B', 'B', 'A', 'B', 'A', 'A'],
'2' => ['B', 'B', 'A', 'A', 'B', 'A'],
'3' => ['B', 'B', 'A', 'A', 'A', 'B'],
'4' => ['B', 'A', 'B', 'B', 'A', 'A'],
'5' => ['B', 'A', 'A', 'B', 'B', 'A'],
'6' => ['B', 'A', 'A', 'A', 'B', 'B'],
'7' => ['B', 'A', 'B', 'A', 'B', 'A'],
'8' => ['B', 'A', 'B', 'A', 'A', 'B'],
'9' => ['B', 'A', 'A', 'B', 'A', 'B'],
],
[
'0' => ['A', 'A', 'A', 'B', 'B', 'B'],
'1' => ['A', 'A', 'B', 'A', 'B', 'B'],
'2' => ['A', 'A', 'B', 'B', 'A', 'B'],
'3' => ['A', 'A', 'B', 'B', 'B', 'A'],
'4' => ['A', 'B', 'A', 'A', 'B', 'B'],
'5' => ['A', 'B', 'B', 'A', 'A', 'B'],
'6' => ['A', 'B', 'B', 'B', 'A', 'A'],
'7' => ['A', 'B', 'A', 'B', 'A', 'B'],
'8' => ['A', 'B', 'A', 'B', 'B', 'A'],
'9' => ['A', 'B', 'B', 'A', 'B', 'A'],
],
];
$seq = '101'; // left guard bar
if ($this->upce) {
$barcode = new Barcode($upce_code);
$p = $upce_parities[$code[1]][$checksumDigit];
for ($i = 0; $i < 6; $i++) {
$seq .= $codes[$p[$i]][$upce_code[$i]];
}
$seq .= '010101'; // right guard bar
} else {
$barcode = new Barcode($code);
$half_len = (int) ceil($length / 2);
if ($length === 8) {
for ($i = 0; $i < $half_len; $i++) {
$seq .= $codes['A'][$code[$i]];
}
} else {
$p = $parities[$code[0]];
for ($i = 1; $i < $half_len; $i++) {
$seq .= $codes[$p[$i - 1]][$code[$i]];
}
}
$seq .= '01010'; // center guard bar
for ($i = $half_len; $i < $length; $i++) {
if (! isset($codes['C'][$code[$i]])) {
throw new InvalidCharacterException('Char '.$code[$i].' not allowed');
}
$seq .= $codes['C'][$code[$i]];
}
$seq .= '101'; // right guard bar
}
return BinarySequenceConverter::generate($seq, $barcode);
}
protected function calculateChecksumDigit(string $code): int
{
// calculate check digit
$sum_a = 0;
for ($i = 1; $i < $this->length - 1; $i += 2) {
$sum_a += (int) $code[$i];
}
if ($this->length > 12) {
$sum_a *= 3;
}
$sum_b = 0;
for ($i = 0; $i < $this->length - 1; $i += 2) {
$sum_b += (int) $code[$i];
}
if ($this->length < 13) {
$sum_b *= 3;
}
$checksumDigit = ($sum_a + $sum_b) % 10;
if ($checksumDigit > 0) {
$checksumDigit = (10 - $checksumDigit);
}
return $checksumDigit;
}
}

View File

@@ -0,0 +1,549 @@
<?php
namespace AgeekDev\Barcode\Types;
use AgeekDev\Barcode\Barcode;
use AgeekDev\Barcode\BarcodeBar;
use AgeekDev\Barcode\Exceptions\BarcodeException;
/*
* IMB - Intelligent Mail Barcode - Onecode - USPS-B-3200
* (requires PHP bcmath extension)
* Intelligent Mail barcode is a 65-bar code for use on mail in the United States.
* The fields are described as follows:<ul><li>The Barcode Identifier shall be assigned by USPS to encode the
* presort identification that is currently printed in human-readable form on the optional endorsement line (OEL)
* as well as for future USPS use. This shall be two digits, with the second digit in the range of 04. The
* allowable encoding ranges shall be 0004, 1014, 2024, 3034, 4044, 5054, 6064, 7074, 8084, and
* 9094.</li><li>The Service Type Identifier shall be assigned by USPS for any combination of services requested
* on the mailpiece. The allowable encoding range shall be 000http://it2.php.net/manual/en/function.dechex.php999.
* Each 3-digit value shall correspond to a particular mail class with a particular combination of service(s). Each
* service program, such as OneCode Confirm and OneCode ACS, shall provide the list of Service Type Identifier
* values.</li><li>The Mailer or Customer Identifier shall be assigned by USPS as a unique, 6 or 9 digits number
* that identifies a business entity. The allowable encoding range for the 6 digit Mailer ID shall be 000000-
* 899999, while the allowable encoding range for the 9 digit Mailer ID shall be 900000000-999999999.</li><li>The
* Serial or Sequence Number shall be assigned by the mailer for uniquely identifying and tracking mailpieces. The
* allowable encoding range shall be 000000000999999999 when used with a 6 digit Mailer ID and 000000-999999 when
* used with a 9 digit Mailer ID. e. The Delivery Point ZIP Code shall be assigned by the mailer for routing the
* mailpiece. This shall replace POSTNET for routing the mailpiece to its final delivery point. The length may be
* 0, 5, 9, or 11 digits. The allowable encoding ranges shall be no ZIP Code, 0000099999, 000000000999999999,
* and 0000000000099999999999.</li></ul>
*
* code to print, separate the ZIP (routing code) from the rest using a minus char '-'
* (BarcodeID_ServiceTypeID_MailerID_SerialNumber-RoutingCode)
*/
class TypeIntelligentMailBarcode implements TypeInterface
{
/**
* @throws BarcodeException
*/
public function getBarcodeData(string $code): Barcode
{
$asc_chr = [
4,
0,
2,
6,
3,
5,
1,
9,
8,
7,
1,
2,
0,
6,
4,
8,
2,
9,
5,
3,
0,
1,
3,
7,
4,
6,
8,
9,
2,
0,
5,
1,
9,
4,
3,
8,
6,
7,
1,
2,
4,
3,
9,
5,
7,
8,
3,
0,
2,
1,
4,
0,
9,
1,
7,
0,
2,
4,
6,
3,
7,
1,
9,
5,
8,
];
$dsc_chr = [
7,
1,
9,
5,
8,
0,
2,
4,
6,
3,
5,
8,
9,
7,
3,
0,
6,
1,
7,
4,
6,
8,
9,
2,
5,
1,
7,
5,
4,
3,
8,
7,
6,
0,
2,
5,
4,
9,
3,
0,
1,
6,
8,
2,
0,
4,
5,
9,
6,
7,
5,
2,
6,
3,
8,
5,
1,
9,
8,
7,
4,
0,
2,
6,
3,
];
$asc_pos = [
3,
0,
8,
11,
1,
12,
8,
11,
10,
6,
4,
12,
2,
7,
9,
6,
7,
9,
2,
8,
4,
0,
12,
7,
10,
9,
0,
7,
10,
5,
7,
9,
6,
8,
2,
12,
1,
4,
2,
0,
1,
5,
4,
6,
12,
1,
0,
9,
4,
7,
5,
10,
2,
6,
9,
11,
2,
12,
6,
7,
5,
11,
0,
3,
2,
];
$dsc_pos = [
2,
10,
12,
5,
9,
1,
5,
4,
3,
9,
11,
5,
10,
1,
6,
3,
4,
1,
10,
0,
2,
11,
8,
6,
1,
12,
3,
8,
6,
4,
4,
11,
0,
6,
1,
9,
11,
5,
3,
7,
3,
10,
7,
11,
8,
2,
10,
3,
5,
8,
0,
3,
12,
11,
8,
4,
5,
1,
3,
0,
7,
12,
9,
8,
10,
];
$code_arr = explode('-', $code);
$tracking_number = $code_arr[0];
$routing_code = $code_arr[1] ?? '';
// Conversion of Routing Code
$binary_code = match (strlen($routing_code)) {
0 => 0,
5 => bcadd($routing_code, '1'),
9 => bcadd($routing_code, '100001'),
11 => bcadd($routing_code, '1000100001'),
default => throw new BarcodeException('Routing code unknown'),
};
$binary_code = bcmul($binary_code, 10);
$binary_code = bcadd($binary_code, $tracking_number[0]);
$binary_code = bcmul($binary_code, 5);
$binary_code = bcadd($binary_code, $tracking_number[1]);
$binary_code .= substr($tracking_number, 2, 18);
// convert to hexadecimal
$binary_code = $this->dec_to_hex($binary_code);
// pad to get 13 bytes
$binary_code = str_pad($binary_code, 26, '0', STR_PAD_LEFT);
// convert string to array of bytes
$binary_code_arr = chunk_split($binary_code, 2, "\r");
$binary_code_arr = substr($binary_code_arr, 0, -1);
$binary_code_arr = explode("\r", $binary_code_arr);
// calculate frame check sequence
$fcs = $this->imb_crc11fcs($binary_code_arr);
// exclude first 2 bits from first byte
$first_byte = sprintf('%2s', dechex((hexdec($binary_code_arr[0]) << 2) >> 2));
$binary_code_102bit = $first_byte.substr($binary_code, 2);
// convert binary data to codewords
$codewords = [];
$data = $this->hex_to_dec($binary_code_102bit);
$codewords[0] = bcmod($data, 636) * 2;
$data = bcdiv($data, 636);
for ($i = 1; $i < 9; $i++) {
$codewords[$i] = bcmod($data, 1365);
$data = bcdiv($data, 1365);
}
$codewords[9] = $data;
if (($fcs >> 10) === 1) {
$codewords[9] += 659;
}
// generate lookup tables
$table2of13 = $this->imb_tables(2, 78);
$table5of13 = $this->imb_tables(5, 1287);
// convert codewords to characters
$characters = [];
$bitmask = 512;
foreach ($codewords as $val) {
if ($val <= 1286) {
$chrcode = $table5of13[$val];
} else {
$chrcode = $table2of13[($val - 1287)];
}
if (($fcs & $bitmask) > 0) {
// bitwise invert
$chrcode = ((~$chrcode) & 8191);
}
$characters[] = $chrcode;
$bitmask /= 2;
}
$characters = array_reverse($characters);
// build bars
$barcode = new Barcode($code);
for ($i = 0; $i < 65; $i++) {
$asc = (($characters[$asc_chr[$i]] & (2 ** $asc_pos[$i])) > 0);
$dsc = (($characters[$dsc_chr[$i]] & (2 ** $dsc_pos[$i])) > 0);
if ($asc && $dsc) {
// full bar (F)
$p = 0;
$h = 3;
} elseif ($asc) {
// ascender (A)
$p = 0;
$h = 2;
} elseif ($dsc) {
// descender (D)
$p = 1;
$h = 2;
} else {
// tracker (T)
$p = 1;
$h = 1;
}
$barcode->addBar(new BarcodeBar(1, $h, true, $p));
if ($i < 64) {
$barcode->addBar(new BarcodeBar(1, 2, false, 0));
}
}
return $barcode;
}
/**
* Convert large integer number to hexadecimal representation.
* (requires PHP bcmath extension)
*
* @param $number (string) number to convert specified as a string
*/
protected function dec_to_hex(string $number): string
{
if ($number === '0') {
return '00';
}
$hex = [];
while ($number > 0) {
$hex[] = strtoupper(dechex(bcmod($number, '16')));
$number = bcdiv($number, '16');
}
$hex = array_reverse($hex);
return implode($hex);
}
/**
* Intelligent Mail Barcode calculation of Frame Check Sequence
*
* @param $code_arr array of hexadecimal values (13 bytes holding 102 bits right justified).
* @return int 11 bit Frame Check Sequence as integer (decimal base)
*/
protected function imb_crc11fcs(array $code_arr): int
{
$genpoly = 0x0F35; // generator polynomial
$fcs = 0x07FF; // Frame Check Sequence
// do most significant byte skipping the 2 most significant bits
$data = hexdec($code_arr[0]) << 5;
for ($bit = 2; $bit < 8; $bit++) {
if (($fcs ^ $data) & 0x400) {
$fcs = ($fcs << 1) ^ $genpoly;
} else {
$fcs <<= 1;
}
$fcs &= 0x7FF;
$data <<= 1;
}
// do rest of bytes
for ($byte = 1; $byte < 13; $byte++) {
$data = hexdec($code_arr[$byte]) << 3;
for ($bit = 0; $bit < 8; $bit++) {
if (($fcs ^ $data) & 0x400) {
$fcs = ($fcs << 1) ^ $genpoly;
} else {
$fcs <<= 1;
}
$fcs &= 0x7FF;
$data <<= 1;
}
}
return $fcs;
}
/**
* Convert large hexadecimal number to decimal representation (string).
* (requires PHP bcmath extension)
*
* @param $hex (string) hexadecimal number to convert specified as a string
* @return int|string hexadecimal representation
*/
protected function hex_to_dec(string $hex): int|string
{
$dec = 0;
$bitval = 1;
$len = strlen($hex);
for ($pos = ($len - 1); $pos >= 0; $pos--) {
$dec = bcadd($dec, bcmul(hexdec($hex[$pos]), $bitval));
$bitval = bcmul($bitval, 16);
}
return $dec;
}
/**
* generate Nof13 tables used for Intelligent Mail Barcode
*
* @param $n (int) is the type of table: 2 for 2of13 table, 5 for 5of13table
* @param $size (int) size of table (78 for n=2 and 1287 for n=5)
*/
protected function imb_tables(int $n, int $size): array
{
$table = [];
$lli = 0; // LUT lower index
$lui = $size - 1; // LUT upper index
for ($count = 0; $count < 8192; $count++) {
$bit_count = 0;
for ($bit_index = 0; $bit_index < 13; $bit_index++) {
$bit_count += (int) (($count & (1 << $bit_index)) !== 0);
}
// if we don't have the right number of bits on, go on to the next value
if ($bit_count === $n) {
$reverse = ($this->imb_reverse_us($count) >> 3);
// if the reverse is less than count, we have already visited this pair before
if ($reverse >= $count) {
// If count is symmetric, place it at the first free slot from the end of the list.
// Otherwise, place it at the first free slot from the beginning of the list && place $reverse ath the next free slot from the beginning of the list
if ($reverse === $count) {
$table[$lui] = $count;
$lui--;
} else {
$table[$lli] = $count;
$lli++;
$table[$lli] = $reverse;
$lli++;
}
}
}
}
return $table;
}
/**
* Reverse unsigned short value
*
* @param $num (int) value to reverse
*/
protected function imb_reverse_us(int $num): int
{
$rev = 0;
for ($i = 0; $i < 16; $i++) {
$rev <<= 1;
$rev |= ($num & 1);
$num >>= 1;
}
return $rev;
}
}

View File

@@ -0,0 +1,10 @@
<?php
namespace AgeekDev\Barcode\Types;
use AgeekDev\Barcode\Barcode;
interface TypeInterface
{
public function getBarcodeData(string $code): Barcode;
}

View File

@@ -0,0 +1,17 @@
<?php
namespace AgeekDev\Barcode\Types;
/*
* Interleaved 2 of 5 barcodes.
* Compact numeric code, widely used in industry, air cargo
* Contains digits (0 to 9) and encodes the data in the width of both bars and spaces.
*/
class TypeInterleaved25 extends TypeInterleaved25Checksum
{
protected function getChecksum(string $code): string
{
return '';
}
}

View File

@@ -0,0 +1,79 @@
<?php
namespace AgeekDev\Barcode\Types;
use AgeekDev\Barcode\Barcode;
use AgeekDev\Barcode\BarcodeBar;
use AgeekDev\Barcode\Exceptions\InvalidCharacterException;
use AgeekDev\Barcode\Helpers\BarcodeHelper;
/*
* Interleaved 2 of 5 barcodes.
* Compact numeric code, widely used in industry, air cargo
* Contains digits (0 to 9) and encodes the data in the width of both bars and spaces.
*/
class TypeInterleaved25Checksum implements TypeInterface
{
/**
* @throws InvalidCharacterException
*/
public function getBarcodeData(string $code): Barcode
{
$chr['0'] = '11221';
$chr['1'] = '21112';
$chr['2'] = '12112';
$chr['3'] = '22111';
$chr['4'] = '11212';
$chr['5'] = '21211';
$chr['6'] = '12211';
$chr['7'] = '11122';
$chr['8'] = '21121';
$chr['9'] = '12121';
$chr['A'] = '11';
$chr['Z'] = '21';
// add checksum
$code .= $this->getChecksum($code);
if ((strlen($code) % 2) != 0) {
// add leading zero if code-length is odd
$code = '0'.$code;
}
// add start and stop codes
$code = 'AA'.strtolower($code).'ZA';
$barcode = new Barcode($code);
for ($i = 0, $iMax = strlen($code); $i < $iMax; $i = ($i + 2)) {
$char_bar = $code[$i];
$char_space = $code[$i + 1];
if (! isset($chr[$char_bar], $chr[$char_space])) {
throw new InvalidCharacterException;
}
// create a bar-space sequence
$seq = '';
$chrlen = strlen($chr[$char_bar]);
for ($s = 0; $s < $chrlen; $s++) {
$seq .= $chr[$char_bar][$s].$chr[$char_space][$s];
}
for ($j = 0, $jMax = strlen($seq); $j < $jMax; $j++) {
if (($j % 2) === 0) {
$t = true; // bar
} else {
$t = false; // space
}
$w = $seq[$j];
$barcode->addBar(new BarcodeBar($w, 1, $t));
}
}
return $barcode;
}
protected function getChecksum(string $code): string
{
return (string) BarcodeHelper::getChecksum($code);
}
}

View File

@@ -0,0 +1,16 @@
<?php
namespace AgeekDev\Barcode\Types;
/*
* RMS4CC - CBC - KIX
* RMS4CC (Royal Mail 4-state Customer Code) - CBC (Customer Bar Code) - KIX (Klant index - Customer index)
* RM4SCC is the name of the barcode symbology used by the Royal Mail for its Cleanmail service.
* @param $kix (boolean) if true prints the KIX variation (doesn't use the start and end symbols, and the checksum)
* - in this case the house number must be sufficed with an X and placed at the end of the code.
*/
class TypeKix extends TypeRms4cc
{
protected bool $kix = true;
}

View File

@@ -0,0 +1,17 @@
<?php
namespace AgeekDev\Barcode\Types;
/*
* MSI.
* Variation of Plessey code, with similar applications
* Contains digits (0 to 9) and encodes the data only in the width of bars.
*
* @param $code (string) code to represent.
* @param $checksum (boolean) if true add a checksum to the code (modulo 11)
*/
class TypeMsi extends TypeMsiChecksum
{
protected bool $checksum = false;
}

View File

@@ -0,0 +1,74 @@
<?php
namespace AgeekDev\Barcode\Types;
use AgeekDev\Barcode\Barcode;
use AgeekDev\Barcode\Exceptions\InvalidCharacterException;
use AgeekDev\Barcode\Helpers\BinarySequenceConverter;
/*
* MSI.
* Variation of Plessey code, with similar applications
* Contains digits (0 to 9) and encodes the data only in the width of bars.
*
* @param $code (string) code to represent.
* @param $checksum (boolean) if true add a checksum to the code (modulo 11)
*/
class TypeMsiChecksum implements TypeInterface
{
protected bool $checksum = true;
/**
* @throws InvalidCharacterException
*/
public function getBarcodeData(string $code): Barcode
{
$chr['0'] = '100100100100';
$chr['1'] = '100100100110';
$chr['2'] = '100100110100';
$chr['3'] = '100100110110';
$chr['4'] = '100110100100';
$chr['5'] = '100110100110';
$chr['6'] = '100110110100';
$chr['7'] = '100110110110';
$chr['8'] = '110100100100';
$chr['9'] = '110100100110';
$chr['A'] = '110100110100';
$chr['B'] = '110100110110';
$chr['C'] = '110110100100';
$chr['D'] = '110110100110';
$chr['E'] = '110110110100';
$chr['F'] = '110110110110';
if ($this->checksum) {
// add checksum
$clen = strlen($code);
$p = 2;
$check = 0;
for ($i = ($clen - 1); $i >= 0; $i--) {
$check += (hexdec($code[$i]) * $p);
$p++;
if ($p > 7) {
$p = 2;
}
}
$check %= 11;
if ($check > 0) {
$check = 11 - $check;
}
$code .= $check;
}
$seq = '110'; // left guard
$clen = strlen($code);
for ($i = 0; $i < $clen; $i++) {
$digit = $code[$i];
if (! isset($chr[$digit])) {
throw new InvalidCharacterException('Char '.$digit.' is unsupported');
}
$seq .= $chr[$digit];
}
$seq .= '1001'; // right guard
return BinarySequenceConverter::convert($code, $seq);
}
}

View File

@@ -0,0 +1,36 @@
<?php
namespace AgeekDev\Barcode\Types;
use AgeekDev\Barcode\Barcode;
use AgeekDev\Barcode\Helpers\BinarySequenceConverter;
/*
* Pharmacode
* Contains digits (0 to 9)
*/
class TypePharmacode implements TypeInterface
{
public function getBarcodeData(string|int $code): Barcode
{
$code = (int) $code;
$seq = '';
while ($code > 0) {
if (($code % 2) === 0) {
$seq .= '11100';
$code -= 2;
} else {
$seq .= '100';
$code--;
}
$code /= 2;
}
$seq = substr($seq, 0, -2);
$seq = strrev($seq);
return BinarySequenceConverter::convert($code, $seq);
}
}

View File

@@ -0,0 +1,78 @@
<?php
namespace AgeekDev\Barcode\Types;
/*
* Pharmacode two-track
* Contains digits (0 to 9)
*/
use AgeekDev\Barcode\Barcode;
use AgeekDev\Barcode\BarcodeBar;
use AgeekDev\Barcode\Exceptions\InvalidLengthException;
class TypePharmacodeTwoCode implements TypeInterface
{
/**
* @throws InvalidLengthException
*/
public function getBarcodeData(string|float $code): Barcode
{
$code = (float) $code;
if ($code < 1) {
throw new InvalidLengthException('Pharmacode 2 needs a number of 1 or larger');
}
$seq = '';
do {
switch ($code % 3) {
case 0:
$seq .= '3';
$code = ($code - 3) / 3.0;
break;
case 1:
$seq .= '1';
$code = ($code - 1) / 3.0;
break;
case 2:
$seq .= '2';
$code = ($code - 2) / 3.0;
break;
}
} while ($code != 0);
$seq = strrev($seq);
$barcode = new Barcode($code);
$p = 0;
$h = 1;
for ($i = 0, $iMax = strlen($seq); $i < $iMax; $i++) {
switch ($seq[$i]) {
case '1':
$p = 1;
break;
case '2':
$p = 0;
break;
case '3':
$h = 2;
break;
}
$barcode->addBar(new BarcodeBar(1, $h, 1, $p));
if ($i < (strlen($seq) - 1)) {
$barcode->addBar(new BarcodeBar(1, 2, 0, 0));
}
}
return $barcode;
}
}

View File

@@ -0,0 +1,28 @@
<?php
namespace AgeekDev\Barcode\Types;
/*
* PLANET barcodes.
* Used by U.S. Postal Service for automated mail sorting
*
* @param $code (string) zip code to represent. Must be a string containing a zip code of the form DDDDD or
* DDDDD-DDDD.
* @param $planet (boolean) if true print the PLANET barcode, otherwise print POSTNET
*/
class TypePlanet extends TypePostnet
{
protected array $barlen = [
0 => [1, 1, 2, 2, 2],
1 => [2, 2, 2, 1, 1],
2 => [2, 2, 1, 2, 1],
3 => [2, 2, 1, 1, 2],
4 => [2, 1, 2, 2, 1],
5 => [2, 1, 2, 1, 2],
6 => [2, 1, 1, 2, 2],
7 => [1, 2, 2, 2, 1],
8 => [1, 2, 2, 1, 2],
9 => [1, 2, 1, 2, 2],
];
}

View File

@@ -0,0 +1,69 @@
<?php
namespace AgeekDev\Barcode\Types;
/*
* POSTNET and PLANET barcodes.
* Used by U.S. Postal Service for automated mail sorting
*
* @param $code (string) zip code to represent. Must be a string containing a zip code of the form DDDDD or
* DDDDD-DDDD.
* @param $planet (boolean) if true print the PLANET barcode, otherwise print POSTNET
*/
use AgeekDev\Barcode\Barcode;
use AgeekDev\Barcode\BarcodeBar;
class TypePostnet implements TypeInterface
{
protected array $barlen = [
0 => [2, 2, 1, 1, 1],
1 => [1, 1, 1, 2, 2],
2 => [1, 1, 2, 1, 2],
3 => [1, 1, 2, 2, 1],
4 => [1, 2, 1, 1, 2],
5 => [1, 2, 1, 2, 1],
6 => [1, 2, 2, 1, 1],
7 => [2, 1, 1, 1, 2],
8 => [2, 1, 1, 2, 1],
9 => [2, 1, 2, 1, 1],
];
public function getBarcodeData(string $code): Barcode
{
$code = str_replace(['-', ' '], '', $code);
$len = strlen($code);
$barcode = new Barcode($code);
// calculate checksum
$sum = 0;
for ($i = 0; $i < $len; $i++) {
$sum += (int) $code[$i];
}
$chkd = ($sum % 10);
if ($chkd > 0) {
$chkd = (10 - $chkd);
}
$code .= $chkd;
$len = strlen($code);
// start bar
$barcode->addBar(new BarcodeBar(1, 2, 1));
$barcode->addBar(new BarcodeBar(1, 2, 0));
for ($i = 0; $i < $len; $i++) {
for ($j = 0; $j < 5; $j++) {
$h = $this->barlen[$code[$i]][$j];
$p = floor(1 / $h);
$barcode->addBar(new BarcodeBar(1, $h, 1, $p));
$barcode->addBar(new BarcodeBar(1, 2, 0));
}
}
// end bar
$barcode->addBar(new BarcodeBar(1, 2, 1));
return $barcode;
}
}

View File

@@ -0,0 +1,165 @@
<?php
namespace AgeekDev\Barcode\Types;
/*
* RMS4CC - CBC - KIX
* RMS4CC (Royal Mail 4-state Customer Code) - CBC (Customer Bar Code) - KIX (Klant index - Customer index)
* RM4SCC is the name of the barcode symbology used by the Royal Mail for its Cleanmail service.
* @param $kix (boolean) if true prints the KIX variation (doesn't use the start and end symbols, and the checksum)
* - in this case the house number must be sufficed with an X and placed at the end of the code.
*/
use AgeekDev\Barcode\Barcode;
use AgeekDev\Barcode\BarcodeBar;
class TypeRms4cc implements TypeInterface
{
protected bool $kix = false;
public function getBarcodeData(string $code): Barcode
{
// bar mode
// 1 = pos 1, length 2
// 2 = pos 1, length 3
// 3 = pos 2, length 1
// 4 = pos 2, length 2
$barmode = [
'0' => [3, 3, 2, 2],
'1' => [3, 4, 1, 2],
'2' => [3, 4, 2, 1],
'3' => [4, 3, 1, 2],
'4' => [4, 3, 2, 1],
'5' => [4, 4, 1, 1],
'6' => [3, 1, 4, 2],
'7' => [3, 2, 3, 2],
'8' => [3, 2, 4, 1],
'9' => [4, 1, 3, 2],
'A' => [4, 1, 4, 1],
'B' => [4, 2, 3, 1],
'C' => [3, 1, 2, 4],
'D' => [3, 2, 1, 4],
'E' => [3, 2, 2, 3],
'F' => [4, 1, 1, 4],
'G' => [4, 1, 2, 3],
'H' => [4, 2, 1, 3],
'I' => [1, 3, 4, 2],
'J' => [1, 4, 3, 2],
'K' => [1, 4, 4, 1],
'L' => [2, 3, 3, 2],
'M' => [2, 3, 4, 1],
'N' => [2, 4, 3, 1],
'O' => [1, 3, 2, 4],
'P' => [1, 4, 1, 4],
'Q' => [1, 4, 2, 3],
'R' => [2, 3, 1, 4],
'S' => [2, 3, 2, 3],
'T' => [2, 4, 1, 3],
'U' => [1, 1, 4, 4],
'V' => [1, 2, 3, 4],
'W' => [1, 2, 4, 3],
'X' => [2, 1, 3, 4],
'Y' => [2, 1, 4, 3],
'Z' => [2, 2, 3, 3],
];
$code = strtoupper($code);
$len = strlen($code);
$barcode = new Barcode($code);
if (! $this->kix) {
// table for checksum calculation (row,col)
$checktable = [
'0' => [1, 1],
'1' => [1, 2],
'2' => [1, 3],
'3' => [1, 4],
'4' => [1, 5],
'5' => [1, 0],
'6' => [2, 1],
'7' => [2, 2],
'8' => [2, 3],
'9' => [2, 4],
'A' => [2, 5],
'B' => [2, 0],
'C' => [3, 1],
'D' => [3, 2],
'E' => [3, 3],
'F' => [3, 4],
'G' => [3, 5],
'H' => [3, 0],
'I' => [4, 1],
'J' => [4, 2],
'K' => [4, 3],
'L' => [4, 4],
'M' => [4, 5],
'N' => [4, 0],
'O' => [5, 1],
'P' => [5, 2],
'Q' => [5, 3],
'R' => [5, 4],
'S' => [5, 5],
'T' => [5, 0],
'U' => [0, 1],
'V' => [0, 2],
'W' => [0, 3],
'X' => [0, 4],
'Y' => [0, 5],
'Z' => [0, 0],
];
$row = 0;
$col = 0;
for ($i = 0; $i < $len; $i++) {
$row += $checktable[$code[$i]][0];
$col += $checktable[$code[$i]][1];
}
$row %= 6;
$col %= 6;
$chk = array_keys($checktable, [$row, $col]);
$code .= $chk[0];
$len++;
// start bar
$barcode->addBar(new BarcodeBar(1, 2, 1));
$barcode->addBar(new BarcodeBar(1, 2, 0));
}
for ($i = 0; $i < $len; $i++) {
for ($j = 0; $j < 4; $j++) {
switch ($barmode[$code[$i]][$j]) {
case 1:
$p = 0;
$h = 2;
break;
case 2:
$p = 0;
$h = 3;
break;
case 3:
$p = 1;
$h = 1;
break;
case 4:
$p = 1;
$h = 2;
break;
}
$barcode->addBar(new BarcodeBar(1, $h, 1, $p));
$barcode->addBar(new BarcodeBar(1, 2, 0));
}
}
if (! $this->kix) {
// stop bar
$barcode->addBar(new BarcodeBar(1, 3, 1));
}
return $barcode;
}
}

View File

@@ -0,0 +1,63 @@
<?php
namespace AgeekDev\Barcode\Types;
use AgeekDev\Barcode\Barcode;
use AgeekDev\Barcode\Exceptions\InvalidCharacterException;
use AgeekDev\Barcode\Helpers\BarcodeHelper;
use AgeekDev\Barcode\Helpers\BinarySequenceConverter;
/*
* Standard 2 of 5 barcodes.
* Used in airline ticket marking, photofinishing
* Contains digits (0 to 9) and encodes the data only in the width of bars.
*/
class TypeStandard2of5 implements TypeInterface
{
protected bool $checksum = false;
/**
* @throws InvalidCharacterException
*/
public function getBarcodeData(string $code): Barcode
{
$chr['0'] = '10101110111010';
$chr['1'] = '11101010101110';
$chr['2'] = '10111010101110';
$chr['3'] = '11101110101010';
$chr['4'] = '10101110101110';
$chr['5'] = '11101011101010';
$chr['6'] = '10111011101010';
$chr['7'] = '10101011101110';
$chr['8'] = '11101010111010';
$chr['9'] = '10111010111010';
if ($this->checksum) {
// add checksum
$code .= $this->checksum_s25($code);
}
$seq = '11011010';
for ($i = 0, $iMax = strlen($code); $i < $iMax; $i++) {
$digit = $code[$i];
if (! isset($chr[$digit])) {
throw new InvalidCharacterException('Char '.$digit.' is unsupported');
}
$seq .= $chr[$digit];
}
$seq .= '1101011';
return BinarySequenceConverter::convert($code, $seq);
}
/**
* Checksum for standard 2 of 5 barcodes.
*
* @param $code (string) code to process.
* @return int checksum.
*/
protected function checksum_s25(string $code): int
{
return BarcodeHelper::getChecksum($code);
}
}

View File

@@ -0,0 +1,14 @@
<?php
namespace AgeekDev\Barcode\Types;
/*
* Standard 2 of 5 barcodes.
* Used in airline ticket marking, photofinishing
* Contains digits (0 to 9) and encodes the data only in the width of bars.
*/
class TypeStandard2of5Checksum extends TypeStandard2of5
{
protected bool $checksum = true;
}

View File

@@ -0,0 +1,22 @@
<?php
namespace AgeekDev\Barcode\Types;
/*
* EAN13 and UPC-A barcodes.
* EAN13: European Article Numbering international retail product code
* UPC-A: Universal product code seen on almost all retail products in the USA and Canada
* UPC-E: Short version of UPC symbol
*
* @param $code (string) code to represent.
* @param $len (string) barcode type: 6 = UPC-E, 8 = EAN8, 13 = EAN13, 12 = UPC-A
*/
class TypeUpcA extends TypeEanUpcBase
{
protected int $length = 12;
protected bool $upca = true;
protected bool $upce = false;
}

View File

@@ -0,0 +1,22 @@
<?php
namespace AgeekDev\Barcode\Types;
/*
* EAN13 and UPC-A barcodes.
* EAN13: European Article Numbering international retail product code
* UPC-A: Universal product code seen on almost all retail products in the USA and Canada
* UPC-E: Short version of UPC symbol
*
* @param $code (string) code to represent.
* @param $len (string) barcode type: 6 = UPC-E, 8 = EAN8, 13 = EAN13, 12 = UPC-A
*/
class TypeUpcE extends TypeEanUpcBase
{
protected int $length = 12;
protected bool $upca = false;
protected bool $upce = true;
}

View File

@@ -0,0 +1,98 @@
<?php
namespace AgeekDev\Barcode\Types;
use AgeekDev\Barcode\Barcode;
use AgeekDev\Barcode\Exceptions\InvalidCheckDigitException;
use AgeekDev\Barcode\Helpers\BinarySequenceConverter;
/*
* UPC-Based Extensions
* 2-Digit Ext.: Used to indicate magazines and newspaper issue numbers
* 5-Digit Ext.: Used to mark suggested retail price of books
*/
class TypeUpcExtension2 implements TypeInterface
{
protected int $length = 2;
/**
* @throws InvalidCheckDigitException
*/
public function getBarcodeData(string $code): Barcode
{
$len = $this->length;
// Padding
$code = str_pad($code, $len, '0', STR_PAD_LEFT);
// calculate check digit
if ($len === 2) {
$r = (int) $code % 4;
} elseif ($len === 5) {
$r = (3 * ((int) $code[0] + (int) $code[2] + (int) $code[4])) + (9 * ((int) $code[1] + (int) $code[3]));
$r %= 10;
} else {
throw new InvalidCheckDigitException;
}
// Convert digits to bars
$codes = [
'A' => [ // left odd parity
'0' => '0001101',
'1' => '0011001',
'2' => '0010011',
'3' => '0111101',
'4' => '0100011',
'5' => '0110001',
'6' => '0101111',
'7' => '0111011',
'8' => '0110111',
'9' => '0001011',
],
'B' => [ // left even parity
'0' => '0100111',
'1' => '0110011',
'2' => '0011011',
'3' => '0100001',
'4' => '0011101',
'5' => '0111001',
'6' => '0000101',
'7' => '0010001',
'8' => '0001001',
'9' => '0010111',
],
];
$parities = [
2 => [
'0' => ['A', 'A'],
'1' => ['A', 'B'],
'2' => ['B', 'A'],
'3' => ['B', 'B'],
],
5 => [
'0' => ['B', 'B', 'A', 'A', 'A'],
'1' => ['B', 'A', 'B', 'A', 'A'],
'2' => ['B', 'A', 'A', 'B', 'A'],
'3' => ['B', 'A', 'A', 'A', 'B'],
'4' => ['A', 'B', 'B', 'A', 'A'],
'5' => ['A', 'A', 'B', 'B', 'A'],
'6' => ['A', 'A', 'A', 'B', 'B'],
'7' => ['A', 'B', 'A', 'B', 'A'],
'8' => ['A', 'B', 'A', 'A', 'B'],
'9' => ['A', 'A', 'B', 'A', 'B'],
],
];
$p = $parities[$len][$r];
$seq = '1011'; // left guard bar
$seq .= $codes[$p[0]][$code[0]];
for ($i = 1; $i < $len; $i++) {
$seq .= '01'; // separator
$seq .= $codes[$p[$i]][$code[$i]];
}
return BinarySequenceConverter::convert($code, $seq);
}
}

View File

@@ -0,0 +1,14 @@
<?php
namespace AgeekDev\Barcode\Types;
/*
* UPC-Based Extensions
* 2-Digit Ext.: Used to indicate magazines and newspaper issue numbers
* 5-Digit Ext.: Used to mark suggested retail price of books
*/
class TypeUpcExtension5 extends TypeUpcExtension2
{
protected int $length = 5;
}

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2016 Anand Siddharth
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,321 @@
# Laravel Paytm Wallet
[![Latest Stable Version](https://poser.pugx.org/anandsiddharth/laravel-paytm-wallet/v/stable)](https://packagist.org/packages/anandsiddharth/laravel-paytm-wallet)
[![Total Downloads](https://poser.pugx.org/anandsiddharth/laravel-paytm-wallet/downloads)](https://packagist.org/packages/anandsiddharth/laravel-paytm-wallet)
[![License](https://poser.pugx.org/anandsiddharth/laravel-paytm-wallet/license)](https://packagist.org/packages/anandsiddharth/laravel-paytm-wallet)
[![Join the chat at https://gitter.im/laravel-paytm-wallet/Lobby](https://badges.gitter.im/laravel-paytm-wallet/Lobby.svg)](https://gitter.im/laravel-paytm-wallet/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
**For Laravel 5.0** use version `^1.0.0` <br />
**For Laravel 6.0** use version `^1.0.0` <br />
**For Laravel 7.0** use version `^1.0.0` <br />
**For Laravel 8.0** use version `^2.0.0` <br />
## Introduction
Integrate paytm wallet in your laravel application easily with this package. This package uses official Paytm PHP SDK's.
## License
Laravel Paytm Wallet open-sourced software licensed under the [MIT license](http://opensource.org/licenses/MIT)
## Getting Started
To get started add the following package to your `composer.json` file using this command.
composer require anandsiddharth/laravel-paytm-wallet
## Configuring
**Note: For Laravel 5.5 and above auto-discovery takes care of below configuration.**
When composer installs Laravel Paytm Wallet library successfully, register the `Anand\LaravelPaytmWallet\PaytmWalletServiceProvider` in your `config/app.php` configuration file.
```php
'providers' => [
// Other service providers...
Anand\LaravelPaytmWallet\PaytmWalletServiceProvider::class,
],
```
Also, add the `PaytmWallet` facade to the `aliases` array in your `app` configuration file:
```php
'aliases' => [
// Other aliases
'PaytmWallet' => Anand\LaravelPaytmWallet\Facades\PaytmWallet::class,
],
```
#### Add the paytm credentials to the `.env` file
```bash
PAYTM_ENVIRONMENT=local
PAYTM_MERCHANT_ID=YOUR_MERCHANT_ID_HERE
PAYTM_MERCHANT_KEY=YOUR_SECRET_KEY_HERE
PAYTM_MERCHANT_WEBSITE=YOUR_MERCHANT_WEBSITE
PAYTM_CHANNEL=YOUR_CHANNEL_HERE
PAYTM_INDUSTRY_TYPE=YOUR_INDUSTRY_TYPE_HERE
```
#### One more step to go....
On your `config/services.php` add the following configuration
```php
'paytm-wallet' => [
'env' => env('PAYTM_ENVIRONMENT'), // values : (local | production)
'merchant_id' => env('PAYTM_MERCHANT_ID'),
'merchant_key' => env('PAYTM_MERCHANT_KEY'),
'merchant_website' => env('PAYTM_MERCHANT_WEBSITE'),
'channel' => env('PAYTM_CHANNEL'),
'industry_type' => env('PAYTM_INDUSTRY_TYPE'),
],
```
Note : All the credentials mentioned are provided by Paytm after signing up as merchant.
#### Laravel 7 Changes
Our package is comptible with Laravel 7 but same_site setting is changed in default Laravel installation, make sure you change `same_site` to `null` in `config/session.php` or callback won't include cookies and you will be logged out when a payment is completed
```php
<?php
use Illuminate\Support\Str;
return [
/...
'same_site' => null,
];
```
## Usage
### Making a transaction
```php
<?php
namespace App\Http\Controllers;
use PaytmWallet;
class OrderController extends Controller
{
/**
* Redirect the user to the Payment Gateway.
*
* @return Response
*/
public function order()
{
$payment = PaytmWallet::with('receive');
$payment->prepare([
'order' => $order->id,
'user' => $user->id,
'mobile_number' => $user->phonenumber,
'email' => $user->email,
'amount' => $order->amount,
'callback_url' => 'http://example.com/payment/status'
]);
return $payment->receive();
}
/**
* Obtain the payment information.
*
* @return Object
*/
public function paymentCallback()
{
$transaction = PaytmWallet::with('receive');
$response = $transaction->response(); // To get raw response as array
//Check out response parameters sent by paytm here -> http://paywithpaytm.com/developer/paytm_api_doc?target=interpreting-response-sent-by-paytm
if($transaction->isSuccessful()){
//Transaction Successful
}else if($transaction->isFailed()){
//Transaction Failed
}else if($transaction->isOpen()){
//Transaction Open/Processing
}
$transaction->getResponseMessage(); //Get Response Message If Available
//get important parameters via public methods
$transaction->getOrderId(); // Get order id
$transaction->getTransactionId(); // Get transaction id
}
}
```
Make sure the `callback_url` you have mentioned while receiving payment is `post` on your `routes.php` file, Example see below:
```php
Route::post('/payment/status', [App\Http\Controllers\PaytmController::class,'paymentCallback'])->name('status');
```
Important: The `callback_url` must not be csrf protected [Check out here to how to do that](https://laracasts.com/discuss/channels/general-discussion/l5-disable-csrf-middleware-on-certain-routes)
### Get transaction status/information using order id
```php
<?php
namespace App\Http\Controllers;
use PaytmWallet;
class OrderController extends Controller
{
/**
* Obtain the transaction status/information.
*
* @return Object
*/
public function statusCheck(){
$status = PaytmWallet::with('status');
$status->prepare(['order' => $order->id]);
$status->check();
$response = $status->response(); // To get raw response as array
//Check out response parameters sent by paytm here -> http://paywithpaytm.com/developer/paytm_api_doc?target=txn-status-api-description
if($status->isSuccessful()){
//Transaction Successful
}else if($status->isFailed()){
//Transaction Failed
}else if($status->isOpen()){
//Transaction Open/Processing
}
$status->getResponseMessage(); //Get Response Message If Available
//get important parameters via public methods
$status->getOrderId(); // Get order id
$status->getTransactionId(); // Get transaction id
}
}
```
### Initiating Refunds
```php
<?php
namespace App\Http\Controllers;
use PaytmWallet;
class OrderController extends Controller
{
/**
* Initiate refund.
*
* @return Object
*/
public function refund(){
$refund = PaytmWallet::with('refund');
$refund->prepare([
'order' => $order->id,
'reference' => "refund-order-4", // provide refund reference for your future reference (should be unique for each order)
'amount' => 300, // refund amount
'transaction' => $order->transaction_id // provide paytm transaction id referring to this order
]);
$refund->initiate();
$response = $refund->response(); // To get raw response as array
if($refund->isSuccessful()){
//Refund Successful
}else if($refund->isFailed()){
//Refund Failed
}else if($refund->isOpen()){
//Refund Open/Processing
}else if($refund->isPending()){
//Refund Pending
}
}
}
```
### Check Refund Status
```php
<?php
namespace App\Http\Controllers;
use PaytmWallet;
class OrderController extends Controller
{
/**
* Initiate refund.
*
* @return Object
*/
public function refund(){
$refundStatus = PaytmWallet::with('refund_status');
$refundStatus->prepare([
'order' => $order->id,
'reference' => "refund-order-4", // provide reference number (the same which you have entered for initiating refund)
]);
$refundStatus->check();
$response = $refundStatus->response(); // To get raw response as array
if($refundStatus->isSuccessful()){
//Refund Successful
}else if($refundStatus->isFailed()){
//Refund Failed
}else if($refundStatus->isOpen()){
//Refund Open/Processing
}else if($refundStatus->isPending()){
//Refund Pending
}
}
}
```
### Customizing transaction being processed page
Considering the modern app user interfaces, default "transaction being processed page" is too dull which comes with this package. If you would like to modify this, you have the option to do so. Here's how:
You just need to change 1 line in you `OrderController`'s code.
```php
<?php
namespace App\Http\Controllers;
use PaytmWallet;
class OrderController extends Controller
{
/**
* Redirect the user to the Payment Gateway.
*
* @return Response
*/
public function order()
{
$payment = PaytmWallet::with('receive');
$payment->prepare([
'order' => $order->id,
'user' => $user->id,
'mobile_number' => $user->phonenumber,
'email' => $user->email,
'amount' => $order->amount,
'callback_url' => 'http://example.com/payment/status'
]);
return $payment->view('your_custom_view')->receive();
}
```
Here `$payment->receive()` is replaced with `$payment->view('your_custom_view')->receive()`. Replace `your_custom_view` with your view name which resides in your `resources/views/your_custom_view.blade.php`.
And in your view file make sure you have added this line of code before `</body>` (i.e. before closing body tag), which redirects to payment gateway.
`@yield('payment_redirect')`
Here's a sample custom view:
```html
<html>
<head>
</head>
<body>
<h1>Custom payment message</h1>
@yield('payment_redirect')
</body>
</html>
```
That's all folks!
## Support on Beerpay
[![Beerpay](https://beerpay.io/anandsiddharth/laravel-paytm-wallet/badge.svg?style=beer-square)](https://beerpay.io/anandsiddharth/laravel-paytm-wallet) [![Beerpay](https://beerpay.io/anandsiddharth/laravel-paytm-wallet/make-wish.svg?style=flat-square)](https://beerpay.io/anandsiddharth/laravel-paytm-wallet?focus=wish)

View File

@@ -0,0 +1,33 @@
{
"name": "anandsiddharth/laravel-paytm-wallet",
"description": "Integrate paytm wallet easily with this package. This package uses official Paytm PHP SDK's",
"license": "MIT",
"keywords": ["paytm wallet", "laravel"],
"authors": [
{
"name": "Anand Siddharth",
"email": "anandsiddharth21@gmail.com"
}
],
"minimum-stability": "dev",
"require": {
"php": ">=7.3.0",
"illuminate/support": ">=8.0",
"illuminate/contracts": ">=8.0"
},
"autoload": {
"psr-4": {
"Anand\\LaravelPaytmWallet\\": "src/"
}
},
"extra": {
"laravel": {
"providers": [
"Anand\\LaravelPaytmWallet\\PaytmWalletServiceProvider"
],
"aliases": {
"PaytmWallet": "Anand\\LaravelPaytmWallet\\Facades\\PaytmWallet"
}
}
}
}

View File

@@ -0,0 +1,154 @@
<?php
function encrypt_e_openssl($input, $ky){
$iv = "@@@@&&&&####$$$$";
$data = openssl_encrypt ( $input , "AES-128-CBC" , $ky, 0, $iv );
return $data;
}
function decrypt_e_openssl($crypt, $ky){
$iv = "@@@@&&&&####$$$$";
$data = openssl_decrypt ( $crypt , "AES-128-CBC" , $ky, 0, $iv );
return $data;
}
function generateSalt_e($length) {
$random = "";
srand((double) microtime() * 1000000);
$data = "AbcDE123IJKLMN67QRSTUVWXYZ";
$data .= "aBCdefghijklmn123opq45rs67tuv89wxyz";
$data .= "0FGH45OP89";
for ($i = 0; $i < $length; $i++) {
$random .= substr($data, (rand() % (strlen($data))), 1);
}
return $random;
}
function checkString_e($value) {
$myvalue = ltrim($value);
$myvalue = rtrim($myvalue);
if (in_array($myvalue, ['null', 'NULL'])) {
$myvalue = '';
}
return $myvalue;
}
function getChecksumFromArray($arrayList, $key, $sort=1) {
if ($sort != 0) {
ksort($arrayList);
}
$str = getArray2Str($arrayList);
$salt = generateSalt_e(4);
$finalString = $str . "|" . $salt;
$hash = hash("sha256", $finalString);
$hashString = $hash . $salt;
$checksum = encrypt_e_openssl($hashString, $key);
return $checksum;
}
function getChecksumFromString($str, $key) {
$salt = generateSalt_e(4);
$finalString = $str . "|" . $salt;
$hash = hash("sha256", $finalString);
$hashString = $hash . $salt;
$checksum = encrypt_e_openssl($hashString, $key);
return $checksum;
}
function verifychecksum_e($arrayList, $key, $checksumvalue) {
$arrayList = removeCheckSumParam($arrayList);
ksort($arrayList);
$str = getArray2Str($arrayList);
$paytm_hash = decrypt_e_openssl($checksumvalue, $key);
$salt = substr($paytm_hash, -4);
$finalString = $str . "|" . $salt;
$website_hash = hash("sha256", $finalString);
$website_hash .= $salt;
$validFlag = "FALSE";
if ($website_hash == $paytm_hash) {
$validFlag = "TRUE";
} else {
$validFlag = "FALSE";
}
return $validFlag;
}
function verifychecksum_eFromStr($str, $key, $checksumvalue) {
$paytm_hash = decrypt_e_openssl($checksumvalue, $key);
$salt = substr($paytm_hash, -4);
$finalString = $str . "|" . $salt;
$website_hash = hash("sha256", $finalString);
$website_hash .= $salt;
$validFlag = "FALSE";
if ($website_hash == $paytm_hash) {
$validFlag = "TRUE";
} else {
$validFlag = "FALSE";
}
return $validFlag;
}
function getArray2Str($arrayList) {
$paramStr = "";
$flag = 1;
foreach ($arrayList as $key => $value) {
if ($flag) {
$paramStr .= checkString_e($value);
$flag = 0;
} else {
$paramStr .= "|" . checkString_e($value);
}
}
return $paramStr;
}
function redirect2PG($paramList, $key) {
$hashString = getchecksumFromArray($paramList);
$checksum = encrypt_e_openssl($hashString, $key);
}
function removeCheckSumParam($arrayList) {
if (isset($arrayList["CHECKSUMHASH"])) {
unset($arrayList["CHECKSUMHASH"]);
}
return $arrayList;
}
function getTxnStatus($requestParamList) {
return callAPI(PAYTM_STATUS_QUERY_URL, $requestParamList);
}
function initiateTxnRefund($requestParamList) {
$CHECKSUM = getChecksumFromArray($requestParamList,PAYTM_MERCHANT_KEY,0);
$requestParamList["CHECKSUM"] = $CHECKSUM;
return callAPI(PAYTM_REFUND_URL, $requestParamList);
}
function callAPI($apiURL, $requestParamList) {
$jsonResponse = "";
$responseParamList = array();
$JsonData =json_encode($requestParamList);
$postData = 'JsonData='.urlencode($JsonData);
$ch = curl_init($apiURL);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");
curl_setopt($ch, CURLOPT_POSTFIELDS, $postData);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt ($ch, CURLOPT_SSL_VERIFYHOST, 0);
curl_setopt ($ch, CURLOPT_SSL_VERIFYPEER, 0);
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
'Content-Type: application/json',
'Content-Length: ' . strlen($postData))
);
$jsonResponse = curl_exec($ch);
$responseParamList = json_decode($jsonResponse,true);
return $responseParamList;
}

View File

@@ -0,0 +1,15 @@
<?php
namespace Anand\LaravelPaytmWallet\Contracts;
interface Factory
{
/**
* Get Paytm Wallet Provider
*
* @param string $driver
* @return \Anand\LaravelPaytmWallet\Contracts\Provider
*/
public function driver($do = null);
}

View File

@@ -0,0 +1,14 @@
<?php
namespace Anand\LaravelPaytmWallet\Contracts;
interface Provider
{
/**
* Return raw response.
*
* @return object
*/
public function response();
}

View File

@@ -0,0 +1,34 @@
<?php
namespace Anand\LaravelPaytmWallet\Facades;
use Illuminate\Support\Facades\Facade;
/**
* @see \Laravel\Socialite\SocialiteManager
*/
class PaytmWallet extends Facade
{
/**
* Get the registered name of the component.
*
* @return string
*/
const STATUS_SUCCESSFUL = 'TXN_SUCCESS';
const STATUS_FAILURE = 'TXN_FAILURE';
const STATUS_OPEN = 'OPEN';
const STATUS_PENDING = 'PENDING';
const RESPONSE_SUCCESSFUL="01";
const RESPONSE_CANCELLED = "141";
const RESPONSE_FAILED = "227";
const RESPONSE_PAGE_CLOSED = "810";
const REPSONSE_REFUND_ALREADY_RAISED = "617";
const RESPONSE_CANCELLED_CUSTOMER = "8102";
const RESPONSE_CANCELLED_CUSTOMER_INSUFFICIENT_BALANCE = "8103";
protected static function getFacadeAccessor()
{
return 'Anand\LaravelPaytmWallet\Contracts\Factory';
}
}

View File

@@ -0,0 +1,76 @@
<?php
namespace Anand\LaravelPaytmWallet;
use Illuminate\Support\Manager;
use Illuminate\Http\Request;
class PaytmWalletManager extends Manager implements Contracts\Factory{
protected $config;
public function with($driver){
return $this->driver($driver);
}
protected function createReceiveDriver(){
$this->config = $this->container['config']['services.paytm-wallet'];
return $this->buildProvider(
'Anand\LaravelPaytmWallet\Providers\ReceivePaymentProvider',
$this->config
);
}
protected function createStatusDriver(){
$this->config = $this->container['config']['services.paytm-wallet'];
return $this->buildProvider(
'Anand\LaravelPaytmWallet\Providers\StatusCheckProvider',
$this->config
);
}
protected function createBalanceDriver(){
$this->config = $this->container['config']['services.paytm-wallet'];
return $this->buildProvider(
'Anand\LaravelPaytmWallet\Providers\BalanceCheckProvider',
$this->config
);
}
protected function createAppDriver(){
$this->config = $this->container['config']['services.paytm-wallet'];
return $this->buildProvider(
'Anand\LaravelPaytmWallet\Providers\PaytmAppProvider',
$this->config
);
}
protected function createRefundDriver() {
$this->config = $this->container['config']['services.paytm-wallet'];
return $this->buildProvider(
'Anand\LaravelPaytmWallet\Providers\RefundPaymentProvider',
$this->config
);
}
protected function createRefundStatusDriver(){
$this->config = $this->container['config']['services.paytm-wallet'];
return $this->buildProvider(
'Anand\LaravelPaytmWallet\Providers\RefundStatusCheckProvider',
$this->config
);
}
public function getDefaultDriver(){
throw new \Exception('No driver was specified. - Laravel Paytm Wallet');
}
public function buildProvider($provider, $config){
return new $provider(
$this->container['request'],
$config
);
}
}

View File

@@ -0,0 +1,42 @@
<?php
namespace Anand\LaravelPaytmWallet;
use Illuminate\Support\ServiceProvider;
class PaytmWalletServiceProvider extends ServiceProvider
{
/**
* Indicates if loading of the provider is deferred.
*
* @var bool
*/
protected $defer = false;
/**
* Register bindings in the container.
*
* @return void
*/
public function register()
{
$this->app->singleton('Anand\LaravelPaytmWallet\Contracts\Factory', function ($app) {
// $this->app->singleton('PaytmWallet', function ($app) {
return new PaytmWalletManager($app);
});
}
public function boot(){
$this->loadViewsFrom(__DIR__.'/resources/views', 'paytmwallet');
}
public function provides(){
return ['Anand\LaravelPaytmWallet\Contracts\Factory'];
}
}

View File

@@ -0,0 +1,49 @@
<?php
namespace Anand\LaravelPaytmWallet\Providers;
use Illuminate\Http\Request;
// require __DIR__.'/../../lib/encdec_paytm.php';
class BalanceCheckProvider extends PaytmWalletProvider{
private $parameters = null;
public function prepare($params = array()){
$defaults = [
'token' => NULL,
];
$_p = array_merge($defaults, $params);
foreach ($_p as $key => $value) {
if ($value == NULL) {
throw new \Exception(' \''.$key.'\' parameter not specified in array passed in prepare() method');
return false;
}
}
$this->parameters = $_p;
return $this;
}
public function check(){
if ($this->parameters == null) {
throw new \Exception("prepare() method not called");
}
return $this->beginTransaction();
}
private function beginTransaction(){
$params = [
'MID' => $this->merchant_id,
'TOKEN' => $this->parameters['token']
];
return $this->api_call($this->paytm_balance_check_url, $params);
}
}

View File

@@ -0,0 +1,38 @@
<?php
namespace Anand\LaravelPaytmWallet\Providers;
use Illuminate\Http\Request;
class PaytmAppProvider extends PaytmWalletProvider{
public function generate(Request $request){
$checksum = getChecksumFromArray($request->all(), $this->merchant_key);
return response()->json([ 'CHECKSUMHASH' => $checksum, 'ORDER_ID' => $request->get('ORDER_ID'), 'payt_STATUS' => '1' ]);
}
public function verify(Request $request, $success = null, $error = null){
$paramList = $request->all();
$return_array = $request->all();
$paytmChecksum = $request->get('CHECKSUMHASH');
$isValidChecksum = verifychecksum_e($paramList, $this->merchant_key, $paytmChecksum);
if ($isValidChecksum) {
if ($success != null && is_callable($success)) {
$success();
}
}else{
if ($error != null && is_callable($error)) {
$error();
}
}
$return_array["IS_CHECKSUM_VALID"] = $isValidChecksum ? "Y" : "N";
unset($return_array["CHECKSUMHASH"]);
$encoded_json = htmlentities(json_encode($return_array));
return view('paytmwallet::app_redirect')->with('json', $encoded_json);
}
}

View File

@@ -0,0 +1,65 @@
<?php
namespace Anand\LaravelPaytmWallet\Providers;
use Anand\LaravelPaytmWallet\Contracts\Provider as ProviderContract;
use Illuminate\Http\Request;
require __DIR__.'/../../lib/encdec_paytm.php';
class PaytmWalletProvider implements ProviderContract {
protected $request;
protected $response;
protected $paytm_txn_url;
protected $paytm_txn_status_url;
protected $paytm_refund_url;
protected $paytm_refund_status_url;
protected $paytm_balance_check_url;
protected $merchant_key;
protected $merchant_id;
protected $merchant_website;
protected $industry_type;
protected $channel;
public function __construct(Request $request, $config){
$this->request = $request;
if ($config['env'] == 'production') {
$domain = 'securegw.paytm.in';
}else{
$domain = 'securegw-stage.paytm.in';
}
$this->paytm_txn_url = 'https://'.$domain.'/theia/processTransaction';
$this->paytm_txn_status_url = 'https://'.$domain.'/merchant-status/getTxnStatus';
$this->paytm_refund_url = 'https://'.$domain.'/refund/HANDLER_INTERNAL/REFUND';
$this->paytm_refund_status_url = 'https://'.$domain.'/refund/HANDLER_INTERNAL/getRefundStatus';
$this->paytm_balance_check_url = 'https://'.$domain.'/refund/HANDLER_INTERNAL/getRefundStatus';
$this->merchant_key = $config['merchant_key'];
$this->merchant_id = $config['merchant_id'];
$this->merchant_website = $config['merchant_website'];
$this->industry_type = $config['industry_type'];
$this->channel = $config['channel'];
}
public function response(){
$checksum = $this->request->get('CHECKSUMHASH');
if(verifychecksum_e($this->request->post(), $this->merchant_key, $checksum) == "TRUE"){
return $this->response = $this->request->post();
}
throw new \Exception('Invalid checksum');
}
public function getResponseMessage() {
return $this->response()['RESPMSG'];
}
public function api_call($url, $params){
return callAPI($url, $params);
}
public function api_call_new($url, $params){
return callAPI($url, $params);
}
}

View File

@@ -0,0 +1,76 @@
<?php
namespace Anand\LaravelPaytmWallet\Providers;
use Anand\LaravelPaytmWallet\Facades\PaytmWallet;
use Anand\LaravelPaytmWallet\Traits\HasTransactionStatus;
use Illuminate\Http\Request;
class ReceivePaymentProvider extends PaytmWalletProvider{
use HasTransactionStatus;
private $parameters = null;
private $view = 'paytmwallet::transact';
public function prepare($params = array()){
$defaults = [
'order' => NULL,
'user' => NULL,
'amount' => NULL,
'callback_url' => NULL,
'email' => NULL,
'mobile_number' => NULL,
];
$_p = array_merge($defaults, $params);
foreach ($_p as $key => $value) {
if ($value == NULL) {
throw new \Exception(' \''.$key.'\' parameter not specified in array passed in prepare() method');
return false;
}
}
$this->parameters = $_p;
return $this;
}
public function receive(){
if ($this->parameters == null) {
throw new \Exception("prepare() method not called");
}
return $this->beginTransaction();
}
public function view($view) {
if($view) {
$this->view = $view;
}
return $this;
}
private function beginTransaction(){
$params = [
'REQUEST_TYPE' => 'DEFAULT',
'MID' => $this->merchant_id,
'ORDER_ID' => $this->parameters['order'],
'CUST_ID' => $this->parameters['user'],
'INDUSTRY_TYPE_ID' => $this->industry_type,
'CHANNEL_ID' => $this->channel,
'TXN_AMOUNT' => $this->parameters['amount'],
'WEBSITE' => $this->merchant_website,
'CALLBACK_URL' => $this->parameters['callback_url'],
'MOBILE_NO' => $this->parameters['mobile_number'],
'EMAIL' => $this->parameters['email'],
];
return view('paytmwallet::form')->with('view', $this->view)->with('params', $params)->with('txn_url', $this->paytm_txn_url)->with('checkSum', getChecksumFromArray($params, $this->merchant_key));
}
public function getOrderId(){
return $this->response()['ORDERID'];
}
public function getTransactionId(){
return $this->response()['TXNID'];
}
}

View File

@@ -0,0 +1,70 @@
<?php
namespace Anand\LaravelPaytmWallet\Providers;
use Anand\LaravelPaytmWallet\Facades\PaytmWallet;
use Anand\LaravelPaytmWallet\Traits\HasTransactionStatus;
use Illuminate\Http\Request;
class RefundPaymentProvider extends PaytmWalletProvider{
use HasTransactionStatus;
private $parameters = null;
protected $response;
public function prepare($params = array()){
$defaults = [
'order' => NULL,
'reference' => NULL,
'amount' => NULL,
'transaction' => NULL
];
$_p = array_merge($defaults, $params);
foreach ($_p as $key => $value) {
if ($value == NULL) {
throw new \Exception(' \''.$key.'\' parameter not specified in array passed in prepare() method');
return false;
}
}
$this->parameters = $_p;
return $this;
}
private function beginTransaction(){
$params = array();
$params["MID"] = $this->merchant_id;
$params["ORDERID"] = $this->parameters['order'];
$params["REFID"] = $this->parameters['reference'];
$params["TXNTYPE"] = 'REFUND';
$params["REFUNDAMOUNT"] = $this->parameters['amount'];
$params["TXNID"] = $this->parameters['transaction'];
$chk = getChecksumFromArray($params, $this->merchant_key);
$params['CHECKSUM'] = $chk;
$this->response = $this->api_call_new($this->paytm_refund_url, $params);
return $this;
}
public function initiate(){
if ($this->parameters == null) {
throw new \Exception("prepare() method not called");
}
$this->beginTransaction();
return $this;
}
public function response(){
return $this->response;
}
public function isRefundAlreadyRaised() {
if ($this->isFailed() && $this->response()['RESPCODE'] == PaytmWallet::REPSONSE_REFUND_ALREADY_RAISED) {
return true;
}
return false;
}
}

Some files were not shown because too many files have changed in this diff Show More