Skip to content

Commit e7b93f4

Browse files
authored
Merge pull request #38 from moyasar/feat/support-payment-splitting
2 parents 1ec509e + b50d4a0 commit e7b93f4

12 files changed

Lines changed: 148 additions & 4 deletions

File tree

example/src/App.tsx

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,15 @@ import {
1616
TokenResponse,
1717
type PaymentResult,
1818
UnexpectedError,
19+
// @ts-ignore
20+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
21+
PaymentSplit,
1922
} from 'react-native-moyasar-sdk';
2023

2124
const paymentConfig = new PaymentConfig({
2225
// givenId: '013d92f2-c67b-49c6-ae03-d7c548c771a2',
2326
publishableApiKey: 'pk_test_U38gMHTgVv4wYCd35Zk1JSEd1ZyMYyA9oQ7T4rKa',
27+
// baseUrl: 'https://apimig.moyasar.com', // Staging base URL
2428
amount: 20001,
2529
currency: 'SAR',
2630
merchantCountryCode: 'SA',
@@ -42,6 +46,20 @@ const paymentConfig = new PaymentConfig({
4246
manual: false,
4347
}),
4448
applyCoupon: true,
49+
// splits: [ publishableApiKey for testing: 'pk_test_uQra5pwtUo9GaenMSS4XgfAmeLhmjUTJwFdXJxsH', baseUrl: 'https://apimig.moyasar.com'
50+
// new PaymentSplit({
51+
// recipientId: '7d2d0797-a2be-40fe-bb1b-1fdec9824c95',
52+
// amount: 10001,
53+
// }),
54+
// new PaymentSplit({
55+
// recipientId: '327680bb-d790-4643-8e10-31455a1ab3a6',
56+
// amount: 10000,
57+
// reference: 'optional-reference-for-split-1fcfcbe9-ba75-4eed',
58+
// description: 'Platform processing fee',
59+
// feeSource: true,
60+
// refundable: true,
61+
// }),
62+
// ],
4563
});
4664

4765
function onPaymentResult(paymentResult: PaymentResult) {

src/index.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,4 @@ export * from './models/api/api_responses/token_response';
3636
export * from './models/api/api_requests/token_request';
3737
export * from './models/api/api_requests/payment_request';
3838
export * from './models/component_models/moyasar_style';
39+
export * from './models/payment_split';

src/models/api/api_requests/payment_request.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,71 @@
1+
import type { PaymentSplit } from '../../payment_split';
12
import type { PaymentRequestSource } from '../sources/payment_request_source';
23

34
/**
45
* Constructs a PaymentRequest object for creating a payment.
56
* @param {givenId | null} [givenId] - Optional UUID for the payment (UUID v4 is recommended). It will be attached with the payment creation request to support idempotency. `It is going be the ID of the created payment`.
7+
* @param {string} [baseUrl='https://api.moyasar.com'] - The base URL for Moyasar API. Defaults to 'https://api.moyasar.com'.
68
* @param {number} amount - The amount to be charged in the smallest currency unit. For example, to charge `SAR 257.58` you will have the [amount] as `25758`. In other words, 10 SAR = 10 * 100 Halalas. Integer values only.
79
* @param {string} [currency='SAR'] - The currency code for the payment. Defaults to 'SAR'. Must be in ISO 4217 3-letter currency code format.
810
* @param {string | null} [description] - Can be any string you want to tag the payment. For example `Payment for Order #34321`.
911
* @param {Record<string, string | number | boolean> | null} [metadata] - Adds searchable key/value pairs to the payment. For example `{"size": "xl"}`.
1012
* @param {PaymentRequestSource} source - A payment source object to be charged, such as Apple Pay source or Credit Card source.
1113
* @param {string | null} [callbackUrl] - The URL to be redirected to after a 3D secure transaction (e.g., https://sdk.moyasar.com/return). Required for Credit Card payments.
1214
* @param {boolean} [applyCoupon=true] - A flag to control the coupon application (based on the BIN). This key is required only if you don't want to apply the coupon. Otherwise, the coupon is going to be applied. Defaults to true.
15+
* @param {PaymentSplit[] | null} [splits] - Optional array of `PaymentSplit` object used to distribute the charged amount (in the smallest currency unit) among multiple recipients or to collect a platform fee.
16+
* - Each split requires `recipientId` and `amount` parameters.
17+
* - `reference` and `description` parameters are optional.
18+
* - Set `feeSource = true` parameter to mark the split as a fee/commission taken by the platform.
19+
* - Set `refundable` parameter to control whether a split amount is refundable (`true`/`false`). Leave it to use the backend's default.
20+
* - Set the `publishableApiKey` to "pk_test_uQra5pwtUo9GaenMSS4XgfAmeLhmjUTJwFdXJxsH" and set the `baseUrl` parameter to "https://apimig.moyasar.com" for staging testing.
1321
*/
1422
export class PaymentRequest {
1523
givenId?: string | null;
24+
baseUrl: string;
1625
amount: number;
1726
currency: string;
1827
description?: string | null;
1928
metadata?: Record<string, string | number | boolean> | null;
2029
source: PaymentRequestSource;
2130
callbackUrl?: string | null;
2231
applyCoupon?: boolean;
32+
splits?: PaymentSplit[] | null;
2333

2434
constructor({
2535
givenId,
36+
baseUrl = 'https://api.moyasar.com',
2637
amount,
2738
currency = 'SAR',
2839
description,
2940
metadata,
3041
source,
3142
callbackUrl,
3243
applyCoupon = true,
44+
splits,
3345
}: {
3446
givenId?: string | null;
47+
baseUrl?: string;
3548
amount: number;
3649
currency?: string;
3750
description?: string | null;
3851
metadata?: Record<string, string | number | boolean> | null;
3952
source: PaymentRequestSource;
4053
callbackUrl?: string | null;
4154
applyCoupon?: boolean;
55+
splits?: PaymentSplit[] | null;
4256
}) {
4357
this.givenId = givenId;
58+
59+
const cleanedBaseUrl = baseUrl.replace(/\/+$/, ''); // Removes trailing slashes
60+
this.baseUrl = cleanedBaseUrl;
4461
this.amount = amount;
4562
this.currency = currency;
4663
this.description = description;
4764
this.metadata = metadata;
4865
this.source = source;
4966
this.callbackUrl = callbackUrl;
5067
this.applyCoupon = applyCoupon;
68+
this.splits = splits;
5169
}
5270

5371
toJson(): Record<string, any> {
@@ -60,6 +78,7 @@ export class PaymentRequest {
6078
source: this.source.toJson(),
6179
callback_url: this.callbackUrl,
6280
apply_coupon: this.applyCoupon ?? true,
81+
splits: this.splits?.map((split) => split.toJson()),
6382
};
6483
}
6584
}

src/models/api/api_requests/token_request.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
* @param {string} cvc - Credit Card's security code.
66
* @param {string} month - Two digit number representing the Credit Card's expiration month.
77
* @param {string} year - Two or four digit number representing the Credit Card's expiration year.
8+
* @param {string} [baseUrl='https://api.moyasar.com'] baseUrl - The base URL for Moyasar API. Defaults to 'https://api.moyasar.com'.
89
* @param {string} callbackUrl - The URL to be redirected to after a 3D secure transaction (e.g., https://sdk.moyasar.com/return).
910
* @param {Record<string, string | number | boolean> | null} [metadata] - Adds searchable key/value pairs to the payment. For example `{"size": "xl"}`.
1011
*/
@@ -14,6 +15,7 @@ export class TokenRequest {
1415
cvc: string;
1516
month: string;
1617
year: string;
18+
baseUrl: string;
1719
callbackUrl: string;
1820
metadata?: Record<string, string | number | boolean> | null;
1921

@@ -23,6 +25,7 @@ export class TokenRequest {
2325
cvc,
2426
month,
2527
year,
28+
baseUrl = 'https://api.moyasar.com',
2629
callbackUrl,
2730
metadata,
2831
}: {
@@ -31,6 +34,7 @@ export class TokenRequest {
3134
cvc: string;
3235
month: string;
3336
year: string;
37+
baseUrl?: string;
3438
callbackUrl: string;
3539
metadata?: Record<string, string | number | boolean> | null;
3640
}) {
@@ -39,6 +43,9 @@ export class TokenRequest {
3943
this.cvc = cvc;
4044
this.month = month;
4145
this.year = year;
46+
47+
const cleanedBaseUrl = baseUrl.replace(/\/+$/, ''); // Removes trailing slashes
48+
this.baseUrl = cleanedBaseUrl;
4249
this.callbackUrl = callbackUrl;
4350
this.metadata = metadata;
4451
}

src/models/api/api_responses/payment_response.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { PaymentSplit } from '../../payment_split';
12
import type { PaymentStatus } from '../../payment_status';
23
import { PaymentType } from '../../payment_type';
34
import { ApplePayPaymentResponseSource } from '../sources/apple_pay/apple_pay_response_source';
@@ -32,6 +33,7 @@ export class PaymentResponse {
3233
updatedAt: string;
3334
metadata?: Record<string, string | number | boolean> | null;
3435
source: PaymentResponseSource;
36+
splits?: PaymentSplit[] | null;
3537

3638
constructor({
3739
id,
@@ -56,6 +58,7 @@ export class PaymentResponse {
5658
updatedAt,
5759
metadata,
5860
source,
61+
splits,
5962
}: {
6063
id: string;
6164
status: PaymentStatus;
@@ -79,6 +82,7 @@ export class PaymentResponse {
7982
updatedAt: string;
8083
metadata?: Record<string, string | number | boolean> | null;
8184
source: PaymentResponseSource;
85+
splits?: PaymentSplit[] | null;
8286
}) {
8387
this.id = id;
8488
this.status = status;
@@ -102,6 +106,7 @@ export class PaymentResponse {
102106
this.updatedAt = updatedAt;
103107
this.metadata = metadata;
104108
this.source = source;
109+
this.splits = splits;
105110
}
106111

107112
/**
@@ -153,6 +158,11 @@ export class PaymentResponse {
153158
updatedAt: json.updated_at,
154159
metadata: json.metadata,
155160
source: paymentSource,
161+
splits: json.splits
162+
? (json.splits as Array<Record<string, any>>).map((splitJson) =>
163+
PaymentSplit.fromJson(splitJson)
164+
)
165+
: null,
156166
});
157167
}
158168
}

src/models/payment_config.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { assert } from '../helpers/assert';
22
import type { ApplePayConfig } from './apple_pay_config';
33
import { CreditCardConfig } from './credit_card_config';
4+
import type { PaymentSplit } from './payment_split';
45
import type { SamsungPayConfig } from './samsung_pay_config';
56

67
/**
@@ -9,6 +10,7 @@ import type { SamsungPayConfig } from './samsung_pay_config';
910
export class PaymentConfig {
1011
givenId?: string | null;
1112
publishableApiKey: string;
13+
baseUrl: string;
1214
amount: number;
1315
merchantCountryCode: string;
1416
currency: string;
@@ -20,11 +22,13 @@ export class PaymentConfig {
2022
createSaveOnlyToken: boolean;
2123
samsungPay?: SamsungPayConfig;
2224
applyCoupon?: boolean;
25+
splits?: PaymentSplit[] | null;
2326

2427
/**
2528
* Constructs a new PaymentConfig instance with the provided settings.
2629
* @param givenId - Optional UUID for the payment (UUID v4 is recommended). It will be attached with the payment creation request to support idempotency. `It is going be the ID of the created payment`.
2730
* @param publishableApiKey - Your Moyasar publishable API key - https://docs.moyasar.com/get-your-api-keys.
31+
* @param baseUrl - Moyasar API base URL. Defaults to `https://api.moyasar.com`.
2832
* @param amount - The amount to be charged in the smallest currency unit. For example, to charge `SAR 257.58` you will have the [amount] as `25758`. In other words, 10 SAR = 10 * 100 Halalas. Integer values only.
2933
* @param merchantCountryCode - The country code of the merchant’s principle place of business. Defaults to 'SA'. Must be in ISO 3166-1 alpha-2 country code format.
3034
* @param currency - The currency code for the payment. Defaults to 'SAR'. Must be in ISO 4217 3-letter currency code format.
@@ -36,10 +40,17 @@ export class PaymentConfig {
3640
* @param createSaveOnlyToken - Optional to process a save only token flow for a Credit Card. Defaults to false - https://docs.moyasar.com/create-token
3741
* @param samsungPay - Required for Samsung Pay feature.
3842
* @param applyCoupon - A flag to control the coupon application (based on the BIN). This key is required only if you don't want to apply the coupon. Otherwise, the coupon is going to be applied. Defaults to true.
43+
* @param splits - Optional array of `PaymentSplit` object used to distribute the charged amount (in the smallest currency unit) among multiple recipients or to collect a platform fee.
44+
* - Each split requires `recipientId` and `amount` parameters.
45+
* - `reference` and `description` parameters are optional.
46+
* - Set `feeSource = true` parameter to mark the split as a fee/commission taken by the platform.
47+
* - Set `refundable` parameter to control whether a split amount is refundable (`true`/`false`). Leave it to use the backend's default.
48+
* - Set the `publishableApiKey` parameter to "pk_test_uQra5pwtUo9GaenMSS4XgfAmeLhmjUTJwFdXJxsH" and set the `baseUrl` parameter to "https://apimig.moyasar.com" for staging testing.
3949
*/
4050
constructor({
4151
givenId,
4252
publishableApiKey,
53+
baseUrl = 'https://api.moyasar.com',
4354
amount,
4455
merchantCountryCode = 'SA',
4556
currency = 'SAR',
@@ -51,9 +62,11 @@ export class PaymentConfig {
5162
createSaveOnlyToken = false,
5263
samsungPay,
5364
applyCoupon = true,
65+
splits,
5466
}: {
5567
givenId?: string | null;
5668
publishableApiKey: string;
69+
baseUrl?: string;
5770
amount: number;
5871
merchantCountryCode?: string;
5972
currency?: string;
@@ -65,6 +78,7 @@ export class PaymentConfig {
6578
createSaveOnlyToken?: boolean;
6679
samsungPay?: SamsungPayConfig;
6780
applyCoupon?: boolean;
81+
splits?: PaymentSplit[] | null;
6882
}) {
6983
assert(
7084
publishableApiKey.length > 0,
@@ -85,9 +99,13 @@ export class PaymentConfig {
8599
supportedNetworks.length > 0,
86100
'At least 1 network must be supported.'
87101
);
102+
assert(baseUrl.length > 0, 'Please fill `baseUrl` argument.');
88103

89104
this.givenId = givenId;
90105
this.publishableApiKey = publishableApiKey;
106+
107+
const cleanedBaseUrl = baseUrl.replace(/\/+$/, ''); // Removes trailing slashes
108+
this.baseUrl = cleanedBaseUrl;
91109
this.amount = amount;
92110
this.merchantCountryCode = merchantCountryCode.toUpperCase();
93111
this.currency = currency;
@@ -101,5 +119,6 @@ export class PaymentConfig {
101119
this.createSaveOnlyToken = createSaveOnlyToken;
102120
this.samsungPay = samsungPay;
103121
this.applyCoupon = applyCoupon;
122+
this.splits = splits;
104123
}
105124
}

src/models/payment_split.ts

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/**
2+
* @param splits - Optional array of `PaymentSplit` object used to distribute the charged amount (in the smallest currency unit) among multiple recipients or to collect a platform fee.
3+
* - Each split requires `recipientId` and `amount` parameters.
4+
* - `reference` and `description` parameters are optional.
5+
* - Set `feeSource = true` parameter to mark the split as a fee/commission taken by the platform.
6+
* - Set `refundable` parameter to control whether a split amount is refundable (`true`/`false`). Leave it to use the backend's default.
7+
* - Set the `publishableApiKey` parameter to "pk_test_uQra5pwtUo9GaenMSS4XgfAmeLhmjUTJwFdXJxsH" and set the `baseUrl` parameter to "https://apimig.moyasar.com" for staging testing.
8+
*/
9+
export class PaymentSplit {
10+
public recipientId: string;
11+
public amount: number;
12+
public reference?: string | null;
13+
public description?: string | null;
14+
public feeSource?: boolean | null;
15+
public refundable?: boolean | null;
16+
17+
constructor({
18+
recipientId,
19+
amount,
20+
reference,
21+
description,
22+
feeSource,
23+
refundable,
24+
}: {
25+
recipientId: string;
26+
amount: number;
27+
reference?: string | null;
28+
description?: string | null;
29+
feeSource?: boolean | null;
30+
refundable?: boolean | null;
31+
}) {
32+
this.recipientId = recipientId;
33+
this.amount = amount;
34+
this.reference = reference;
35+
this.description = description;
36+
this.feeSource = feeSource;
37+
this.refundable = refundable;
38+
}
39+
40+
toJson(): Record<string, any> {
41+
return {
42+
recipient_id: this.recipientId,
43+
amount: this.amount,
44+
...(this.reference && { reference: this.reference }),
45+
...(this.description && { description: this.description }),
46+
...(this.feeSource !== undefined && { fee_source: this.feeSource }),
47+
...(this.refundable !== undefined && { refundable: this.refundable }),
48+
};
49+
}
50+
51+
static fromJson(json: Record<string, any>): PaymentSplit {
52+
return new PaymentSplit({
53+
recipientId: json.recipient_id,
54+
amount: json.amount,
55+
reference: json.reference,
56+
description: json.description,
57+
feeSource: json.fee_source,
58+
refundable: json.refundable,
59+
});
60+
}
61+
}

src/services/credit_card_payment_service.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,13 +110,15 @@ export class CreditCardPaymentService {
110110

111111
const paymentRequest = new PaymentRequest({
112112
givenId: paymentConfig.givenId,
113+
baseUrl: paymentConfig.baseUrl,
113114
amount: paymentConfig.amount,
114115
currency: paymentConfig.currency,
115116
description: paymentConfig.description,
116117
metadata: paymentConfig.metadata,
117118
source: creditCardRequestSource,
118119
callbackUrl: 'https://sdk.moyasar.com/return',
119120
applyCoupon: paymentConfig.applyCoupon,
121+
splits: paymentConfig.splits,
120122
});
121123

122124
const response = await createPayment(
@@ -156,6 +158,7 @@ export class CreditCardPaymentService {
156158
cvc: creditCardRequestSource.cvc,
157159
month: creditCardRequestSource.month,
158160
year: creditCardRequestSource.year,
161+
baseUrl: paymentConfig.baseUrl,
159162
callbackUrl: 'https://sdk.moyasar.com/return',
160163
metadata: paymentConfig.metadata,
161164
});

0 commit comments

Comments
 (0)