Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🚧 add a donation screen #3089

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
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
27 changes: 24 additions & 3 deletions _locales/_untranslated_en.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,6 @@
"broadcast_list_warning": {
"message": "Broadcast Lists are always unencrypted because they use blind copy (BCC)"
},
"pref_donate": {
"message": "Donate"
},
"clear_chat": {
"message": "Clear Chat"
},
Expand Down Expand Up @@ -61,5 +58,29 @@
},
"save_sticker": {
"message": "Save Sticker"
},
"pref_support_deltachat": {
"message": "Donate Money or Time"
},
"contribute_to_deltachat": {
"message": "Contribute to DeltaChat"
},
"donate_money": {
"message": "Donate Money"
},
"other_ways_to_contribute": {
"message": "Other ways to contribute"
},
"other_ways_to_contribute_description": {
"message": "If giving money is not your thing, you can find other ways to help here, like translating Delta Chat into your language, testing nightly versions, giving feedback on in our forum or even getting involved with coding, there are many ways in which you can contribute to the project."
},
"donate_money_reason":{
"message": "Your support helps us to continue to improve Delta Chat."
},
"in_app_donate_no_permission": {
"message": "The user is not allowed to make in-app purchase."
},
"in_app_donate_not_availible": {
"message": "in-app donation failed to load, please conect to the internet"
}
}
16 changes: 16 additions & 0 deletions docs/DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -267,3 +267,19 @@ https://developer.apple.com/library/archive/documentation/Miscellaneous/Referenc

If you want to debug how many jsonrpc calls were made you can run `exp.printCallCounterResult()` in the devConsole when you have debug logging enabled.
This can be useful if you want to test your debouncing logic or compare a branch against another branch, to see if you change reduced overall jsonrpc calls.

### MacOS in app purchase stuff for donations on MacOS:

To test it you need to replace the bundle id:

```
sed -i s/com.github.Electron/chat.delta.desktop.electron/ node_modules/electron/dist/Electron.app/Contents/Info.plist
```

> note: that the `-i` option does only work with the gnu version of `sed`.


#### Useful links:

- https://help.apple.com/app-store-connect/#/devb57be10e7
- Create a Sandbox Apple ID - https://help.apple.com/app-store-connect/#/dev8b997bee1
58 changes: 58 additions & 0 deletions scss/dialogs/_settings.scss
Original file line number Diff line number Diff line change
Expand Up @@ -208,3 +208,61 @@
padding-top: 0px;
}
}

div.donate-page {
margin: 4px;
display: flex;
flex-direction: column;
height: 100%;
max-height: 395px;

p.donate-reason {
font-size: 23px;
line-height: 30px;
}

.donate-money-section {
display: flex;
flex-grow: 1;
align-items: center;
}

.donate-time-section {
.other-ways {
font-size: 18px;
line-height: 23px;
margin-bottom: 17px;
}
}

.spacer {
flex-grow: 1;
}

button {
&:hover {
background-color: #5c85ff;
}

background-color: #7396ff;
color: black;
border: none;
border-radius: 5px;
padding: 10px 15px;
font-size: 36px;
display: flex;
font-size: 23px;
text-align: start;
width: 100%;
.label {
flex-grow: 1;
}
.Icon {
width: 24px;
height: 24px;
mask-size: 80%;
-webkit-mask-size: 80%;
background-color: black;
}
}
}
152 changes: 152 additions & 0 deletions src/main/inAppPurchases.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import { inAppPurchase } from 'electron'
import { platform } from 'os'
import { getLogger } from '../shared/logger'

const log = getLogger('main/inAppDonations')

// Listen for transactions as soon as possible.
inAppPurchase.on(
'transactions-updated',
async (_event: any, transactions: Electron.Transaction[]) => {
if (!Array.isArray(transactions)) {
log.info('No Transactions to process')
return
}

// Check each transaction.
transactions.forEach(transaction => {
const payment = transaction.payment

switch (transaction.transactionState) {
case 'purchasing':
log.info(`Purchasing ${payment.productIdentifier}...`)
break

case 'purchased': {
log.info(`${payment.productIdentifier} purchased.`)

// Get the receipt url.
const receiptURL = inAppPurchase.getReceiptURL()

log.info(`Receipt URL: ${receiptURL}`)

// just assume that the receipt is valid, there is no reason to fake it.

// TODO try to load product information if not loaded yet, if not availible and failed return and let app try again later

// TODO send thank you device message over jsonrpc -> to active account

// Finish the transaction.
inAppPurchase.finishTransactionByDate(transaction.transactionDate)

break
}

case 'failed':
log.info(`Failed to purchase ${payment.productIdentifier}.`)

// Finish the transaction.
inAppPurchase.finishTransactionByDate(transaction.transactionDate)

break
case 'restored':
log.info(
`The purchase of ${payment.productIdentifier} has been restored.`
)

break
case 'deferred':
log.info(
`The purchase of ${payment.productIdentifier} has been deferred.`
)

break
default:
break
}
})
}
)

const PRODUCT_IDS = ['donation.small', 'default.monthly']

let loaded_products: Electron.Product[] | null = null

async function loadProducts(): Promise<Electron.Product[]> {
if (!loaded_products) {
// Retrieve and display the product descriptions.
const products = await inAppPurchase.getProducts(PRODUCT_IDS)

// Check the parameters.
if (!Array.isArray(products) || products.length <= 0) {
throw new Error('Unable to retrieve the product informations.')
}

products.forEach(product => {
log.info(
`The price of ${product.localizedTitle} is ${product.formattedPrice}.`
)
product.subscriptionPeriod
})

loaded_products = products
return products
} else {
return loaded_products
}
}

async function setupInAppPurchases(): Promise<
| { inAppDonationAvailible: false }
| { inAppDonationAvailible: true; paymentsAllowed: false }
| { inAppDonationAvailible: true; paymentsAllowed: true; error: string }
| {
inAppDonationAvailible: true
paymentsAllowed: true
}
> {
// TODO check if appstore build
if (platform() !== 'darwin') {
// if not mac or not appstore build
return {
inAppDonationAvailible: false,
}
}
if (!inAppPurchase.canMakePayments()) {
return {
inAppDonationAvailible: true,
paymentsAllowed: false,
}
} else {
return {
inAppDonationAvailible: true,
paymentsAllowed: true,
}
}
}

async function purchaseSingle(
productIdentifier: Electron.Product['productIdentifier']
) {
if (await inAppPurchase.purchaseProduct(productIdentifier, 1)) {
log.info('The payment has been added to the payment queue.')
} else {
log.info('The product is not valid:', { productIdentifier })
throw new Error('The product is not valid.')
}
}

// TODO:
// - get availible products
// - show thank you device message you when done

const THANK_YOU_MESSAGE = 'Thank you for donating $1!'
const THANK_YOU_MESSAGE_SUBSCRIPTION =
'Thank you for your monthly support of $1.\n\n Remember that you can cancel anytime on https://apps.apple.com/account/subscriptions'

const FAILED_MESSAGE = 'Your donation of $1 failed. :('



loadProducts().then(console.log)
setupInAppPurchases().then(console.log)
68 changes: 68 additions & 0 deletions src/renderer/components/dialogs/Settings-Donate.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import React from 'react'
import { runtime } from '../../runtime'
import { contributeUrl, donationUrl } from '../../../shared/constants'

export function SettingsDonate({}: {}) {
const tx = window.static_translate

const MoneySection = true /* isMacAppstoreBuild */
? DonateMacOSAppstore
: DonateMoney

return (
<div className='donate-page'>
<p className='donate-reason'>{tx('donate_money_reason')}</p>
<div className='donate-money-section'>
<MoneySection />
</div>
<div className='spacer'></div>
<div className='donate-time-section'>
<p className='other-ways'>
{tx('other_ways_to_contribute_description')}
</p>
<button onClick={() => runtime.openLink(contributeUrl)}>
<span className='label'>{tx('other_ways_to_contribute')}</span>
<div
className='Icon'
style={{
WebkitMask:
'url(../images/icons/open_in_new.svg) no-repeat center',
}}
></div>
</button>
</div>
</div>
)
}

function DonateMoney() {
const tx = window.static_translate
return (
<button onClick={() => runtime.openLink(donationUrl)}>
<span className='label'>{tx('donate_money')}</span>
<div
className='Icon'
style={{
WebkitMask: 'url(../images/icons/open_in_new.svg) no-repeat center',
}}
></div>
</button>
)
}

function DonateMacOSAppstore() {

// only one recuring donation option, because otherwise we'd need to track if the user is already subscribed
return (
<div>

<button
onClick={() =>
runtime.openLink('https://apps.apple.com/account/subscriptions')
}
>
Manage Recuring Donation
</button>
</div>
)
}
17 changes: 17 additions & 0 deletions src/renderer/components/dialogs/Settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import SettingsStoreInstance, {
} from '../../stores/settings'
import { runtime } from '../../runtime'
import { donationUrl } from '../../../shared/constants'
import { SettingsDonate } from './Settings-Donate'

export function flipDeltaBoolean(value: string) {
return value === '1' ? '0' : '1'
Expand Down Expand Up @@ -357,6 +358,22 @@ export default function Settings(props: DialogProps) {
</DeltaDialogBody>
</>
)}
{settingsMode === 'donation' && (
<>
<DeltaDialogHeader
title={tx('contribute_to_deltachat')}
showBackButton={true}
onClickBack={() => setSettingsMode('main')}
showCloseButton={true}
onClose={onClose}
/>
<DeltaDialogBody>
<Card elevation={Elevation.ONE}>
<SettingsDonate />
</Card>
</DeltaDialogBody>
</>
)}
</>
)
}
Expand Down
1 change: 1 addition & 0 deletions src/shared/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export const gitHubUrl = 'https://github.com/deltachat/deltachat-desktop'
export const gitHubIssuesUrl = gitHubUrl + '/issues'
export const gitHubLicenseUrl = gitHubUrl + '/blob/master/LICENSE'
export const donationUrl = 'https://delta.chat/donate'
export const contributeUrl = 'https://delta.chat/contribute'

export const appWindowTitle = appName

Expand Down