Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 86 additions & 0 deletions .detoxrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/** @type {Detox.DetoxConfig} */
module.exports = {
logger: {
level: process.env.CI ? 'debug' : undefined,
},
testRunner: {
args: {
$0: 'jest',
config: 'e2e/jest.config.js',
},
jest: {
setupTimeout: 120000,
},
},
apps: {
'ios.debug': {
type: 'ios.app',
binaryPath: 'ios/build/Build/Products/Debug-iphonesimulator/SubTrackr.app',
build:
'xcodebuild -workspace ios/subtrackr.xcworkspace -scheme subtrackr -configuration Debug -sdk iphonesimulator -derivedDataPath ios/build',
},
'ios.release': {
type: 'ios.app',
binaryPath: 'ios/build/Build/Products/Release-iphonesimulator/SubTrackr.app',
build:
'xcodebuild -workspace ios/subtrackr.xcworkspace -scheme subtrackr -configuration Release -sdk iphonesimulator -derivedDataPath ios/build',
},
'android.debug': {
type: 'android.apk',
binaryPath: 'android/app/build/outputs/apk/debug/app-debug.apk',
build: 'cd android && ./gradlew assembleDebug assembleAndroidTest -DtestBuildType=debug',
reversePorts: [8081],
},
'android.release': {
type: 'android.apk',
binaryPath: 'android/app/build/outputs/apk/release/app-release.apk',
build: 'cd android && ./gradlew assembleRelease assembleAndroidTest -DtestBuildType=release',
},
},
devices: {
simulator: {
type: 'ios.simulator',
device: {
type: 'iPhone 15',
},
},
attached: {
type: 'android.attached',
device: {
adbName: '.*',
},
},
emulator: {
type: 'android.emulator',
device: {
avdName: 'Pixel_4_API_30',
},
},
},
configurations: {
'ios.sim.debug': {
device: 'simulator',
app: 'ios.debug',
},
'ios.sim.release': {
device: 'simulator',
app: 'ios.release',
},
'android.att.debug': {
device: 'attached',
app: 'android.debug',
},
'android.att.release': {
device: 'attached',
app: 'android.release',
},
'android.emu.debug': {
device: 'emulator',
app: 'android.debug',
},
'android.emu.release': {
device: 'emulator',
app: 'android.release',
},
},
};
6 changes: 3 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ name: CI/CD Pipeline

on:
push:
branches: [main, dev, develop]
branches: [main, dev, develop, 'feature/*']
pull_request:
branches: [main, dev, develop]
branches: [main, dev, develop, 'feature/*']

env:
NODE_VERSION: '20'
RUST_VERSION: '1.85'
RUST_VERSION: 'stable'

jobs:
commitlint:
Expand Down
61 changes: 61 additions & 0 deletions .github/workflows/e2e-detox.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
name: E2E Detox Tests

on:
push:
branches: [ "main" ]

jobs:
test-ios:
name: Detox iOS
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci --legacy-peer-deps || npm install --legacy-peer-deps
- name: Expo Prebuild
run: npx expo prebuild -p ios
- name: Setup Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: '3.2'
- name: Install CocoaPods dependencies
run: cd ios && pod install --repo-update
- name: Install AppleSimulatorUtils
run: brew tap wix/brew && brew install applesimutils
- name: Build Detox iOS
run: npm run e2e:build-ios
- name: Test Detox iOS
run: npm run e2e:test-ios

test-android:
name: Detox Android
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci --legacy-peer-deps || npm install --legacy-peer-deps
- name: Setup Java
uses: actions/setup-java@v3
with:
distribution: 'zulu'
java-version: '17'
- name: Expo Prebuild
run: npx expo prebuild -p android
- name: Build Detox Android
run: npm run e2e:build-android
- name: Detox Android Emulator
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 30
target: default
arch: x86_64
profile: pixel_4
script: npm run e2e:test-android
3 changes: 2 additions & 1 deletion app.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@
"sounds": [],
"enableBackgroundRemoteNotifications": false
}
]
],
"@config-plugins/detox"
]
}
}
6 changes: 3 additions & 3 deletions contracts/DEPLOYMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,10 @@ export ADMIN_ADDRESS="GD..."

## Environment Variables

| Variable | Description | Required For |
|---|---|---|
| Variable | Description | Required For |
| ----------------- | ---------------------------------------------------------------------------------- | ---------------- |
| `SOROBAN_ACCOUNT` | The identity name (configured in Soroban CLI) or secret key to use for deployment. | Testnet, Mainnet |
| `ADMIN_ADDRESS` | The Stellar address that will be set as the contract admin during initialization. | Testnet, Mainnet |
| `ADMIN_ADDRESS` | The Stellar address that will be set as the contract admin during initialization. | Testnet, Mainnet |

## Verification

Expand Down
Binary file added contracts/clippy_output.txt
Binary file not shown.
109 changes: 1 addition & 108 deletions contracts/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -287,12 +287,7 @@ impl SubTrackrContract {
}

/// User pauses their subscription with a specific duration
pub fn pause_by_subscriber(
env: Env,
subscriber: Address,
subscription_id: u64,
duration: u64,
) {
pub fn pause_by_subscriber(env: Env, subscriber: Address, subscription_id: u64, duration: u64) {
subscriber.require_auth();

let mut sub: Subscription = env
Expand Down Expand Up @@ -522,108 +517,6 @@ impl SubTrackrContract {
);
}

/// Request a refund for a subscription (can only be called by the subscriber)
pub fn request_refund(env: Env, subscription_id: u64, amount: i128) {
let mut sub: Subscription = env
.storage()
.persistent()
.get(&DataKey::Subscription(subscription_id))
.expect("Subscription not found");

sub.subscriber.require_auth();

assert!(amount > 0, "Refund amount must be positive");
assert!(
amount <= sub.total_paid,
"Refund amount cannot exceed total paid"
);

sub.refund_requested_amount = amount;

env.storage()
.persistent()
.set(&DataKey::Subscription(subscription_id), &sub);

// Publish event
env.events().publish(
(String::from_str(&env, "refund_requested"), subscription_id),
(sub.subscriber.clone(), amount),
);
}

/// Approve a refund (can only be called by the admin)
pub fn approve_refund(env: Env, subscription_id: u64) {
let mut sub: Subscription = env
.storage()
.persistent()
.get(&DataKey::Subscription(subscription_id))
.expect("Subscription not found");

let admin: Address = env
.storage()
.instance()
.get(&DataKey::Admin)
.expect("Admin not set");
admin.require_auth();

let amount = sub.refund_requested_amount;
assert!(amount > 0, "No pending refund request");

let _plan: Plan = env
.storage()
.persistent()
.get(&DataKey::Plan(sub.plan_id))
.expect("Plan not found");

// TODO: Execute actual token transfer from merchant back to subscriber
// token::Client::new(&env, &plan.token).transfer(
// &plan.merchant, &sub.subscriber, &amount
// );

sub.total_paid -= amount;
sub.refund_requested_amount = 0;

env.storage()
.persistent()
.set(&DataKey::Subscription(subscription_id), &sub);

// Publish event
env.events().publish(
(String::from_str(&env, "refund_approved"), subscription_id),
(sub.subscriber.clone(), amount),
);
}

/// Reject a refund (can only be called by the admin)
pub fn reject_refund(env: Env, subscription_id: u64) {
let mut sub: Subscription = env
.storage()
.persistent()
.get(&DataKey::Subscription(subscription_id))
.expect("Subscription not found");

let admin: Address = env
.storage()
.instance()
.get(&DataKey::Admin)
.expect("Admin not set");
admin.require_auth();

assert!(sub.refund_requested_amount > 0, "No pending refund request");

sub.refund_requested_amount = 0;

env.storage()
.persistent()
.set(&DataKey::Subscription(subscription_id), &sub);

// Publish event
env.events().publish(
(String::from_str(&env, "refund_rejected"), subscription_id),
sub.subscriber.clone(),
);
}

// ── Queries ──

/// Get plan details
Expand Down
Loading
Loading