Skip to content

Commit

Permalink
feat: add revolut pay pm (#1551)
Browse files Browse the repository at this point in the history
  • Loading branch information
charliecruzan-stripe authored Nov 2, 2023
1 parent 1efc5f4 commit fa81790
Show file tree
Hide file tree
Showing 11 changed files with 214 additions and 5 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Unreleased

**Features**

- Added support for RevolutPay bindings

## 0.34.0 - 2023-10-25

**Features**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ class PaymentMethodCreateParamsFactory(
PaymentMethod.Type.PayPal -> createPayPalParams()
PaymentMethod.Type.Affirm -> createAffirmParams()
PaymentMethod.Type.CashAppPay -> createCashAppParams()
PaymentMethod.Type.RevolutPay -> createRevolutPayParams()
else -> {
throw Exception("This paymentMethodType is not supported yet")
}
Expand Down Expand Up @@ -208,6 +209,11 @@ class PaymentMethodCreateParamsFactory(
return PaymentMethodCreateParams.createCashAppPay(billingDetailsParams)
}

@Throws(PaymentMethodCreateParamsException::class)
private fun createRevolutPayParams(): PaymentMethodCreateParams {
return PaymentMethodCreateParams.createRevolutPay(billingDetailsParams)
}

@Throws(PaymentMethodCreateParamsException::class)
fun createParams(clientSecret: String, paymentMethodType: PaymentMethod.Type?, isPaymentIntent: Boolean): ConfirmStripeIntentParams {
try {
Expand All @@ -230,7 +236,8 @@ class PaymentMethodCreateParamsFactory(
PaymentMethod.Type.AuBecsDebit,
PaymentMethod.Type.Klarna,
PaymentMethod.Type.PayPal,
PaymentMethod.Type.CashAppPay -> {
PaymentMethod.Type.CashAppPay,
PaymentMethod.Type.RevolutPay -> {
val params = createPaymentMethodParams(paymentMethodType)

return if (isPaymentIntent) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ internal fun mapPaymentMethodType(type: PaymentMethod.Type?): String {
PaymentMethod.Type.PayPal -> "PayPal"
PaymentMethod.Type.Affirm -> "Affirm"
PaymentMethod.Type.CashAppPay -> "CashApp"
PaymentMethod.Type.RevolutPay -> "RevolutPay"
else -> "Unknown"
}
}
Expand Down Expand Up @@ -154,6 +155,7 @@ internal fun mapToPaymentMethodType(type: String?): PaymentMethod.Type? {
"PayPal" -> PaymentMethod.Type.PayPal
"Affirm" -> PaymentMethod.Type.Affirm
"CashApp" -> PaymentMethod.Type.CashAppPay
"RevolutPay" -> PaymentMethod.Type.RevolutPay
else -> null
}
}
Expand Down
6 changes: 3 additions & 3 deletions example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -374,15 +374,15 @@ PODS:
- StripePayments (= 23.18.0)
- StripePaymentsUI (= 23.18.0)
- StripeUICore (= 23.18.0)
- stripe-react-native (0.33.0):
- stripe-react-native (0.34.0):
- React-Core
- Stripe (~> 23.18.0)
- StripeApplePay (~> 23.18.0)
- StripeFinancialConnections (~> 23.18.0)
- StripePayments (~> 23.18.0)
- StripePaymentSheet (~> 23.18.0)
- StripePaymentsUI (~> 23.18.0)
- stripe-react-native/Tests (0.33.0):
- stripe-react-native/Tests (0.34.0):
- React-Core
- Stripe (~> 23.18.0)
- StripeApplePay (~> 23.18.0)
Expand Down Expand Up @@ -637,7 +637,7 @@ SPEC CHECKSUMS:
RNScreens: 34cc502acf1b916c582c60003dc3089fa01dc66d
SocketRocket: fccef3f9c5cedea1353a9ef6ada904fde10d6608
Stripe: 4092bc51f41ca1758166aef921aa0dd2f0fbc639
stripe-react-native: 782bcf073871e678dd51a0a1bdb7b0eafae25efe
stripe-react-native: 8607258ea7b0e77534840f5c898ccb70cd8900be
StripeApplePay: aedbcb53f5324d527a52a5888bd0eeee25b3ca36
StripeCore: f86db23fb3f984808e6f5d3876397b953bf58a52
StripeFinancialConnections: 0aaddb3593a7cc76b5f01eab185f16ef60798b15
Expand Down
10 changes: 10 additions & 0 deletions example/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ function getKeys(payment_method?: string) {
secret_key = process.env.STRIPE_SECRET_KEY_WECHAT;
break;
case 'paypal':
case 'revolut_pay':
publishable_key = process.env.STRIPE_PUBLISHABLE_KEY_UK;
secret_key = process.env.STRIPE_SECRET_KEY_UK;
break;
Expand Down Expand Up @@ -363,11 +364,20 @@ app.post('/create-setup-intent', async (req, res) => {
},
confirm: true,
};
const revolutPayIntentPayload = {
payment_method_data: {
type: 'revolut_pay',
},
usage: 'off_session',
};

//@ts-ignore
const setupIntent = await stripe.setupIntents.create({
...{ customer: customer.id, payment_method_types },
...(payment_method_types?.includes('paypal') ? payPalIntentPayload : {}),
...(payment_method_types?.includes('revolut_pay')
? revolutPayIntentPayload
: {}),
});

// Send publishable key and SetupIntent details to client
Expand Down
3 changes: 3 additions & 0 deletions example/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import CashAppScreen from './screens/CashAppScreen';
import PaymentSheetDeferredIntentScreen from './screens/PaymentSheetDeferredIntentScreen';
import PaymentSheetDeferredIntentMultiStepScreen from './screens/PaymentSheetDeferredIntentMultiStepScreen';
import CustomerSheetScreen from './screens/CustomerSheetScreen';
import RevolutPayScreen from './screens/RevolutPayScreen';

const Stack = createNativeStackNavigator<RootStackParamList>();

Expand Down Expand Up @@ -89,6 +90,7 @@ export type RootStackParamList = {
PaymentSheetDeferredIntentScreen: undefined;
PaymentSheetDeferredIntentMultiStepScreen: undefined;
CustomerSheetScreen: undefined;
RevolutPayScreen: undefined;
};

declare global {
Expand Down Expand Up @@ -248,6 +250,7 @@ export default function App() {
name="CustomerSheetScreen"
component={CustomerSheetScreen}
/>
<Stack.Screen name="RevolutPayScreen" component={RevolutPayScreen} />
</Stack.Navigator>
</NavigationContainer>
</>
Expand Down
8 changes: 8 additions & 0 deletions example/src/screens/HomeScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,14 @@ export default function HomeScreen() {
}}
/>
</View>
<View style={styles.buttonContainer}>
<Button
title="RevolutPay"
onPress={() => {
navigation.navigate('RevolutPayScreen');
}}
/>
</View>
<View style={styles.buttonContainer}>
<Button
title="WeChat Pay"
Expand Down
155 changes: 155 additions & 0 deletions example/src/screens/RevolutPayScreen.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
import type { BillingDetails } from '@stripe/stripe-react-native';
import React, { useState } from 'react';
import { Alert, StyleSheet, TextInput } from 'react-native';
import {
useConfirmPayment,
useConfirmSetupIntent,
} from '@stripe/stripe-react-native';
import Button from '../components/Button';
import PaymentScreen from '../components/PaymentScreen';
import { API_URL } from '../Config';
import { colors } from '../colors';

export default function RevolutPayScreen() {
const [email, setEmail] = useState('');
const { confirmPayment, loading: loadingPayment } = useConfirmPayment();
const { confirmSetupIntent, loading: loadingSetup } = useConfirmSetupIntent();

const fetchClientSecret = async (intentType: 'setup' | 'payment') => {
const response = await fetch(`${API_URL}/create-${intentType}-intent`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
email,
currency: 'gbp',
payment_method_types: ['revolut_pay'],
}),
});
const { clientSecret, error } = await response.json();

return { clientSecret, error };
};

const handlePayPress = async () => {
const { clientSecret, error: clientSecretError } = await fetchClientSecret(
'payment'
);

if (clientSecretError) {
Alert.alert(`Error`, clientSecretError);
return;
}

const billingDetails: BillingDetails = {
name: 'John Doe',
email,
};

const { error, paymentIntent } = await confirmPayment(clientSecret, {
paymentMethodType: 'RevolutPay',
paymentMethodData: {
billingDetails,
},
});

if (error) {
Alert.alert(`Error code: ${error.code}`, error.message);
console.log('Payment confirmation error', error.message);
} else if (paymentIntent) {
Alert.alert(
'Success',
`The payment was confirmed successfully! currency: ${paymentIntent.currency}`
);
console.log('Success from promise', paymentIntent);
}
};

const handleSetupPress = async () => {
const { clientSecret, error: clientSecretError } = await fetchClientSecret(
'setup'
);

if (clientSecretError) {
Alert.alert(`Error`, clientSecretError);
return;
}

const { error, setupIntent } = await confirmSetupIntent(
clientSecret,
{
paymentMethodType: 'RevolutPay',
paymentMethodData: {
mandateData: {
customerAcceptance: {
online: {
ipAddress: '0.0.0.0',
userAgent: 'chrome',
},
},
},
},
},
{ setupFutureUsage: 'OffSession' }
);

if (error) {
Alert.alert(`Error code: ${error.code}`, error.message);
console.log('Setup confirmation error', error.message);
} else if (setupIntent) {
Alert.alert('Success', `Status: ${setupIntent.status}`);
console.log('Success from promise', setupIntent);
}
};

return (
<PaymentScreen paymentMethod="revolut_pay">
<TextInput
placeholder="E-mail"
autoCapitalize="none"
keyboardType="email-address"
onChange={(value) => setEmail(value.nativeEvent.text)}
style={styles.input}
/>

<Button
variant="primary"
onPress={handlePayPress}
title="Pay"
accessibilityLabel="Pay"
loading={loadingPayment}
/>

<Button
variant="primary"
onPress={handleSetupPress}
title="Setup for later"
accessibilityLabel="Setup for later"
loading={loadingSetup}
/>
</PaymentScreen>
);
}

const styles = StyleSheet.create({
cardField: {
width: '100%',
height: 50,
marginVertical: 30,
},
row: {
flexDirection: 'row',
alignItems: 'center',
marginVertical: 20,
},
text: {
marginLeft: 12,
},
input: {
height: 44,
borderBottomColor: colors.slate,
borderBottomWidth: 1.5,
marginBottom: 20,
},
});
2 changes: 2 additions & 0 deletions ios/Mappers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,7 @@ class Mappers {
case STPPaymentMethodType.payPal: return "PayPal"
case STPPaymentMethodType.affirm: return "Affirm"
case STPPaymentMethodType.cashApp: return "CashApp"
case STPPaymentMethodType.revolutPay: return "RevolutPay"
case STPPaymentMethodType.unknown: return "Unknown"
default: return "Unknown"
}
Expand Down Expand Up @@ -324,6 +325,7 @@ class Mappers {
case "PayPal": return STPPaymentMethodType.payPal
case "Affirm": return STPPaymentMethodType.affirm
case "CashApp": return STPPaymentMethodType.cashApp
case "RevolutPay": return STPPaymentMethodType.revolutPay
default: return STPPaymentMethodType.unknown
}
}
Expand Down
9 changes: 9 additions & 0 deletions ios/PaymentMethodFactory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ class PaymentMethodFactory {
return try createAffirmPaymentMethodParams()
case STPPaymentMethodType.cashApp:
return try createCashAppPaymentMethodParams()
case STPPaymentMethodType.revolutPay:
return try createRevolutPayPaymentMethodParams()
// case STPPaymentMethodType.weChatPay:
// return try createWeChatPayPaymentMethodParams()
default:
Expand Down Expand Up @@ -110,6 +112,8 @@ class PaymentMethodFactory {
return nil
case STPPaymentMethodType.cashApp:
return nil
case STPPaymentMethodType.revolutPay:
return nil
default:
throw PaymentMethodError.paymentNotSupported
}
Expand Down Expand Up @@ -378,6 +382,11 @@ class PaymentMethodFactory {
let params = STPPaymentMethodCashAppParams()
return STPPaymentMethodParams(cashApp: params, billingDetails: billingDetailsParams, metadata: nil)
}

private func createRevolutPayPaymentMethodParams() throws -> STPPaymentMethodParams {
let params = STPPaymentMethodRevolutPayParams()
return STPPaymentMethodParams(revolutPay: params, billingDetails: billingDetailsParams, metadata: nil)
}

func createMandateData() -> STPMandateDataParams? {
if let mandateParams = paymentMethodData?["mandateData"] as? NSDictionary {
Expand Down
11 changes: 10 additions & 1 deletion src/types/PaymentIntent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ export type ConfirmParams =
| USBankAccountParams
| PayPalParams
| AffirmParams
| CashAppParams;
| CashAppParams
| RevolutPayParams;

export type ConfirmOptions = PaymentMethod.ConfirmOptions;

Expand Down Expand Up @@ -270,6 +271,14 @@ export type CashAppParams = {
};
};

export type RevolutPayParams = {
paymentMethodType: 'RevolutPay';
paymentMethodData?: {
billingDetails?: BillingDetails;
mandateData?: MandateData;
};
};

export type CollectBankAccountParams = {
paymentMethodType: 'USBankAccount';
paymentMethodData: {
Expand Down

0 comments on commit fa81790

Please sign in to comment.